Compare commits
175
Commits
2aad1aca98
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
679a703ab2
|
||
|
|
ecfcea5106
|
||
|
|
4da176d507
|
||
|
|
cc9531f19f
|
||
|
|
4ab52329e1
|
||
|
|
2597b3ccd3
|
||
|
|
a751124b8b
|
||
|
|
6732a71eb7 | ||
|
|
e6c6d2533d
|
||
|
|
6199caf0d0 | ||
|
|
d93cb286ef | ||
|
|
3c621db727 | ||
|
|
04edd6de89
|
||
|
|
a1cf06624e
|
||
|
|
c7d7cea8ae
|
||
|
|
ff4f7b3909
|
||
|
|
b3082a3313
|
||
|
|
d926a632d5
|
||
|
|
e40d67df59
|
||
|
|
2dc978e719
|
||
|
|
de6349f2dc
|
||
|
|
b59318e957 | ||
|
|
90aaa6dcda | ||
|
|
9af0462d29
|
||
|
|
208ad95883
|
||
|
|
983434ffb4
|
||
|
|
078d01666e
|
||
|
|
c5d2624a06
|
||
|
|
69de224cbf | ||
|
|
8e4b719425 | ||
|
|
8f48120ba9 | ||
|
|
9d7cddf758
|
||
|
|
d4c40519e5 | ||
|
|
fb454da3bc | ||
|
|
91dabd565c
|
||
|
|
453a8500af | ||
|
|
afbc3e97d7
|
||
|
|
3c4f720749
|
||
|
|
a0491b2bd1
|
||
|
|
fae15b8d55
|
||
|
|
ab14837e39
|
||
|
|
ece3e9dbfb
|
||
|
|
7b02348f64
|
||
|
|
4f62710061 | ||
|
|
c77a52acd3
|
||
|
|
31b89bbac7 | ||
|
|
b541323949
|
||
|
|
6679d19a09 | ||
|
|
6b2cc3aefc
|
||
|
|
f2744a9a60 | ||
|
|
2e82761b7d
|
||
|
|
001c5d3df7 | ||
|
|
8487f124c7
|
||
|
|
a994f9e91c | ||
|
|
43752aa268
|
||
|
|
0a2290640f | ||
|
|
23bf649c5f
|
||
|
|
f5c4016fa6
|
||
|
|
dccf897c9f
|
||
|
|
3aa462a2b8
|
||
|
|
23420b3235
|
||
|
|
a3853a93d8
|
||
|
|
328e30882e
|
||
|
|
1f847bfce0
|
||
|
|
7a6a7339dc
|
||
|
|
7d815b17e8
|
||
|
|
2efbcfc53f
|
||
|
|
f6ead99f48
|
||
|
|
639d139799
|
||
|
|
d75bf4f1b4
|
||
|
|
2b1e99ab47
|
||
|
|
3d826c5407
|
||
|
|
a8d564803b
|
||
|
|
3399149cba
|
||
|
|
f3f6e6356b
|
||
|
|
a824005893
|
||
|
|
b0c6d7f7f5
|
||
|
|
71574b5030
|
||
|
|
53b353eb7f
|
||
|
|
ad9926861b
|
||
|
|
29afe5c89f
|
||
|
|
037588ecf6
|
||
|
|
0a9cfebcad
|
||
|
|
889f8e0768
|
||
|
|
e64e964666
|
||
|
|
bca79d3c5a
|
||
|
|
d49d90a2de
|
||
|
|
2ac8e5c0d4
|
||
|
|
85c753aaa6
|
||
|
|
b424b26700
|
||
|
|
d3028d7703
|
||
|
|
52711a42a2
|
||
|
|
c998d1b8d7
|
||
|
|
5fa849fd81 | ||
|
|
a31a46bdc2 | ||
|
|
8fc27fa9c2 | ||
|
|
2edea59ca3 | ||
|
|
5edfb5b720 | ||
|
|
9077d9837e | ||
|
|
dd672ded3b | ||
|
|
4d6b99fb28 | ||
|
|
51da3e9d83 | ||
|
|
bb5b6b361a | ||
|
|
13d415db2b | ||
|
|
c3a2b6fa0c | ||
|
|
3d8d6a4468 | ||
|
|
fb21fa550a | ||
|
|
321b80f1f8 | ||
|
|
574b235b6f | ||
|
|
8a2731d411 | ||
|
|
21a02fd0e4 | ||
|
|
08e66c81ca | ||
|
|
c009bbf257 | ||
|
|
e6d3fd80b3
|
||
|
|
54375a9530
|
||
|
|
2ee2806e01
|
||
|
|
23cae4bd10
|
||
|
|
8c21d34943
|
||
|
|
4912c4052f
|
||
|
|
c5bbbf06e3 | ||
|
|
76afcd5e13 | ||
|
|
28a88a9bdf | ||
|
|
d278a47b32 | ||
|
|
dc3236fddb | ||
|
|
73d41da07d
|
||
|
|
75fbfa1e16
|
||
|
|
3c967c0cee | ||
|
|
f9d400699f
|
||
|
|
d9b56790c5
|
||
|
|
16ea31289d
|
||
|
|
2ad5bf75d2
|
||
|
|
11f4551fa5
|
||
|
|
090779ae5d
|
||
|
|
45f0fb9688
|
||
|
|
4c7334c46e
|
||
|
|
1c9cedb8f3
|
||
|
|
a018d7997c
|
||
|
|
b7ff4e0981
|
||
|
|
2d8db42b12
|
||
|
|
19126316a7
|
||
|
|
2c185f7e23
|
||
|
|
b0585d00ad
|
||
|
|
b5d2c9ba87
|
||
|
|
51543cb7ca
|
||
|
|
3fd9476fbc
|
||
|
|
1dc0e5293d
|
||
|
|
335fdff433 | ||
|
|
a78c6a604d | ||
|
|
aed37d5a75 | ||
|
|
cff4f7b414 | ||
|
|
877c973e87 | ||
|
|
ce3c44a053 | ||
|
|
af764903e7 | ||
|
|
fcb9e0c468 | ||
|
|
1dc86b9ff0 | ||
|
|
ea8b13b38a | ||
|
|
2f49960149 | ||
|
|
f52bfaa55e | ||
|
|
dfb353af09 | ||
|
|
5328529e18 | ||
|
|
81d26e095a | ||
|
|
6cf690e637 | ||
|
|
f7a9ff9d23 | ||
|
|
577ad993ea | ||
|
|
f0de5378fe | ||
|
|
b01124f99d | ||
|
|
2ce8bebf29 | ||
|
|
618ce00f9e | ||
|
|
1aef0ffca8 | ||
|
|
a89a5576a3 | ||
|
|
b754709f02 | ||
|
|
f7cfad559c | ||
|
|
e97b3ee6ab | ||
|
|
d44dbd3564 | ||
|
|
9969f43ba3 |
@@ -0,0 +1,151 @@
|
||||
# The key below is `description`, NOT `about`.
|
||||
#
|
||||
# GitHub requires `description` on a YAML issue form and rejects the
|
||||
# template without it; Gitea wants `about` but explicitly accepts
|
||||
# `description` as a compatible alias. `description` is therefore the
|
||||
# only spelling that works on both the canonical repo and the mirror.
|
||||
# The markdown templates beside this one still use `about`, which is
|
||||
# correct for their format on both forges.
|
||||
name: Bug report
|
||||
description: Something in the config is broken or behaves unexpectedly
|
||||
labels:
|
||||
- Kind/Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for filing a bug.
|
||||
|
||||
**Title it as a plain description of the problem**, not as a
|
||||
conventional-commit subject — `mv clobbers a symlink when the target
|
||||
exists`, not `fix(mv): ...`. The commit format belongs on the PR that
|
||||
fixes this; the `Kind/` and `Area/` labels carry type and scope here.
|
||||
|
||||
Before filing, please confirm the problem survives a fresh shell
|
||||
(`exec fish`) — a stale function definition in a long-lived session is
|
||||
the single most common false alarm.
|
||||
|
||||
- type: input
|
||||
id: fish-version
|
||||
attributes:
|
||||
label: fish version
|
||||
description: Output of `fish --version`. This config targets fish 4.x.
|
||||
placeholder: fish, version 4.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: Distribution and version, or macOS release.
|
||||
placeholder: Arch Linux (CachyOS), kernel 6.12.4
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: terminal
|
||||
attributes:
|
||||
label: Terminal emulator
|
||||
description: >-
|
||||
Only matters for rendering, key bindings, and color problems. Leave it
|
||||
blank if the bug has nothing to do with those.
|
||||
placeholder: kitty 0.42.1
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
description: >-
|
||||
Which part of the config is affected? Pick the closest match — a
|
||||
maintainer translates this into the matching `Area/` label at triage,
|
||||
since contributors without push access can't set labels themselves.
|
||||
Choose "Not sure" rather than guessing.
|
||||
options:
|
||||
- Not sure
|
||||
- Functions (functions/)
|
||||
- Completions (completions/)
|
||||
- Config and startup (config.fish, conf.d/)
|
||||
- Docs (docs/manual/, man page, docs site)
|
||||
- Tests (tests/)
|
||||
- CI (.github/workflows/)
|
||||
- Integrations (integrations/)
|
||||
- Prompt and theme (themes/)
|
||||
- Opinionated components (C1-C6 toggles)
|
||||
- Scripts (scripts/)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: What's broken
|
||||
description: One or two sentences. Name the function or file if you know it.
|
||||
placeholder: >-
|
||||
`mv` replaces an existing symlink instead of prompting, so the link
|
||||
target is lost with no confirmation.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: >-
|
||||
Exact commands, starting from a fresh shell, that someone else can
|
||||
paste and run. Include any setup needed to reach the broken state.
|
||||
render: fish
|
||||
placeholder: |
|
||||
exec fish
|
||||
mkdir -p /tmp/repro; cd /tmp/repro
|
||||
touch real; ln -s real link
|
||||
mv real link
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What you thought those commands would do.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
description: >-
|
||||
What happened instead. Paste the complete output, including any error
|
||||
text and stack traces — truncated errors are the usual reason a bug
|
||||
report stalls in Status/Need More Info.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: preflight
|
||||
attributes:
|
||||
label: Pre-flight
|
||||
options:
|
||||
- label: I searched the existing issues and this isn't already reported.
|
||||
required: true
|
||||
- label: I reproduced this in a fresh shell (`exec fish`), not a long-lived session.
|
||||
required: true
|
||||
- label: I ran `fish tests/run-tests.fish` and noted the result below (or in the output above).
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: >-
|
||||
Anything else worth knowing: a private overlay in
|
||||
`~/.config/.user-dots/fish/` that may be involved, opinionated
|
||||
components you've disabled, the last commit where it worked. Never
|
||||
paste credentials, tokens, or machine-specific paths you'd rather not
|
||||
publish.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,21 @@
|
||||
# Read by both Gitea (the canonical repo) and GitHub (the mirror).
|
||||
#
|
||||
# The .yml extension is required: Gitea accepts config.yaml or config.yml,
|
||||
# but GitHub only recognizes config.yml and silently ignores config.yaml.
|
||||
# Don't "tidy" this back to .yaml — the chooser on the mirror stops working.
|
||||
#
|
||||
# Blank issues stay enabled deliberately: the three templates cover bugs,
|
||||
# features, and docs, and anything else (a chore, a refactor, a question)
|
||||
# is better served by an empty box than by a template that doesn't fit.
|
||||
blank_issues_enabled: true
|
||||
|
||||
contact_links:
|
||||
- name: Canonical repository and issue tracker
|
||||
url: https://git.rootiest.dev/rootiest/fish-config/issues
|
||||
about: fish-config is developed on Gitea. If you came from the GitHub mirror, please file here instead — the mirror is read-only and issues opened there are easy to miss.
|
||||
- name: Contributing guide
|
||||
url: https://git.rootiest.dev/rootiest/fish-config/src/branch/main/CONTRIBUTING.md
|
||||
about: Branch naming, commit conventions, coding standards, and the label taxonomy.
|
||||
- name: Customization and personal overrides
|
||||
url: https://git.rootiest.dev/rootiest/fish-config/src/branch/main/docs/manual/07-customization.md
|
||||
about: Want to change behavior on just your machine? Use your private overlay — no issue needed.
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: Documentation issue
|
||||
about: Something in the manual, man page, config-help, or docs site is wrong, missing, or unclear
|
||||
labels:
|
||||
- Kind/Documentation
|
||||
---
|
||||
|
||||
<!--
|
||||
Title this as a plain description of the problem:
|
||||
|
||||
config-help shows literal backticks in the customization section
|
||||
|
||||
not `docs(help): ...`. See CONTRIBUTING.md § Labels.
|
||||
|
||||
Docs in this repo are GENERATED. docs/manual/** plus the doc-header
|
||||
comments above each function are the single source of truth;
|
||||
docs/fish-config.md and docs/fish-config.1 are build output and are never
|
||||
hand-edited. So a fix always lands in the source, not in the page where you
|
||||
saw the problem — the Location section below asks for both.
|
||||
|
||||
Delete these comments as you fill it in.
|
||||
-->
|
||||
|
||||
## Location
|
||||
|
||||
<!--
|
||||
Where you saw it, and where it actually comes from.
|
||||
|
||||
- **Where you saw it** — the docs site URL, the `config-help <topic>` you
|
||||
ran, `man fish-config`, or the README section.
|
||||
- **Source file** — the docs/manual/** page, or the function whose
|
||||
doc-header feeds it (e.g. `functions/mv.fish`). If you're not sure which,
|
||||
say so and leave it to triage rather than guessing.
|
||||
|
||||
If the problem appears in one output but not the others — correct on the
|
||||
site, broken in the pager — say which, since that usually points at the
|
||||
rendering pass (docs/codespans.py) rather than the source text.
|
||||
-->
|
||||
|
||||
## Problem
|
||||
|
||||
<!--
|
||||
What's wrong. Quote the current text so it can be found and compared.
|
||||
Common shapes, if it helps you place yours:
|
||||
|
||||
- **Wrong** — documents behavior the code doesn't have.
|
||||
- **Stale** — described a flag or path that has since changed.
|
||||
- **Missing** — a function, flag, or setting with no entry at all. Note
|
||||
that a function with no `# CATEGORY` header is omitted from the manual
|
||||
deliberately, so "missing" may be an intentional opt-out.
|
||||
- **Unclear** — accurate, but a reader can't act on it. Say what you
|
||||
expected to learn and what you concluded instead.
|
||||
- **Renders wrong** — a broken code span, a mangled table, a bad anchor.
|
||||
-->
|
||||
|
||||
## Suggested fix
|
||||
|
||||
<!--
|
||||
Proposed wording or structure, if you have one — a diff-shaped
|
||||
before/after is ideal, but a rough sketch is welcome too. "I don't know
|
||||
what it should say, only that this confused me" is a legitimate and useful
|
||||
report; keep the heading and say that.
|
||||
|
||||
Two constraints on any text under docs/manual/, both enforced by
|
||||
docs/verify-manual.py:
|
||||
|
||||
- No backticks inside an indented block.
|
||||
- No backtick span wrapped across a line break.
|
||||
|
||||
Doc-headers in .fish files take no backticks at all — docs/codespans.py
|
||||
adds code spans when it renders. See CONTRIBUTING.md § Documentation
|
||||
Pipeline.
|
||||
-->
|
||||
|
||||
## Notes
|
||||
|
||||
<!--
|
||||
Anything else — related issues (`Refs #42`), the commit that introduced the
|
||||
problem, other pages with the same mistake. Drop this heading if empty.
|
||||
-->
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: Feature or enhancement request
|
||||
about: Propose new functionality, or an improvement to something that already exists
|
||||
labels:
|
||||
- Kind/Feature
|
||||
---
|
||||
|
||||
<!--
|
||||
Title this as a plain description of what you want, NOT as a
|
||||
conventional-commit subject:
|
||||
|
||||
A picker for switching themes without editing config.fish
|
||||
|
||||
not `feat(theme): add theme picker`. That format belongs on the PR that
|
||||
implements this; here, the Kind/ and Area/ labels carry type and scope.
|
||||
See CONTRIBUTING.md § Labels.
|
||||
|
||||
This template applies Kind/Feature. If you're proposing an improvement to
|
||||
something that already exists rather than genuinely new functionality, say
|
||||
so in the Summary — a maintainer will swap the label to Kind/Enhancement
|
||||
at triage. Contributors without push access can't set labels directly.
|
||||
|
||||
Keep every heading below except Alternatives considered and Notes, which
|
||||
you can drop if they'd be empty. Delete these comments as you go.
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!--
|
||||
What you want, in one or two sentences. Lead with the capability, not the
|
||||
implementation — "a way to preview a theme before committing to it" rather
|
||||
than "add a --preview flag to theme-set".
|
||||
-->
|
||||
|
||||
## Problem
|
||||
|
||||
<!--
|
||||
What's awkward, slow, or impossible today. Be concrete about the situation
|
||||
that led you here: the sequence of commands you run now, what you have to
|
||||
remember, or what goes wrong. A proposal is only as good as the problem it
|
||||
names, and this section is what a reviewer weighs the cost against.
|
||||
-->
|
||||
|
||||
## Proposed behavior
|
||||
|
||||
<!--
|
||||
The concrete shape of the thing. Where they apply:
|
||||
|
||||
- The command or function name, and its flags.
|
||||
- What it prints on success, and what it does on the error paths.
|
||||
- What happens with no arguments, or with a missing dependency.
|
||||
- Whether it's interactive, and what it falls back to when it isn't.
|
||||
|
||||
A short usage sketch in a ```fish block is worth several paragraphs.
|
||||
-->
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
<!--
|
||||
Other approaches you weighed and why you set them aside — including
|
||||
"solve it in my own ~/.config/.user-dots/fish/local.fish instead", which is
|
||||
the right answer for anything genuinely specific to one machine or one
|
||||
person's taste. See CONTRIBUTING.md § Secrets & Machine-Specific Config.
|
||||
|
||||
Drop this heading if there were no real alternatives.
|
||||
-->
|
||||
|
||||
## Scope
|
||||
|
||||
<!--
|
||||
Answer these — they determine how the change has to be built, and getting
|
||||
them wrong late is expensive:
|
||||
|
||||
- Does this shadow a builtin or an existing command?
|
||||
- Does it run at startup, or bind a key, or set an environment variable?
|
||||
- Does it need a new external dependency, and what should happen when that
|
||||
dependency is missing?
|
||||
- Is it opinionated enough that users should be able to turn it off? If any
|
||||
of the above is yes, it likely needs a `# COMPONENT` header and an
|
||||
`__fish_config_op_enabled` guard — see CONTRIBUTING.md § Opinionated
|
||||
Components.
|
||||
- Does it need a manual entry (a `# CATEGORY` header), and under which of
|
||||
the docs/manual/05-functions/ categories?
|
||||
-->
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
<!--
|
||||
What must be true for this issue to close, as a checkbox list. This is the
|
||||
issue-side counterpart to a PR's ## Verification: it's the shared
|
||||
definition of done, agreed before the work starts rather than argued about
|
||||
after.
|
||||
|
||||
- One observable outcome per line — behavior a reader could check, not
|
||||
implementation steps.
|
||||
- Cover the error and fallback paths, not just the happy one.
|
||||
- Include the docs and tests the change will owe.
|
||||
|
||||
Leave the boxes unchecked; they get ticked as the work lands.
|
||||
-->
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
|
||||
## Notes
|
||||
|
||||
<!--
|
||||
Anything else: prior art in other shells or dotfiles, links to the relevant
|
||||
upstream tool's docs, related issues (`Refs #42`). Drop this heading if
|
||||
there's nothing to add.
|
||||
-->
|
||||
@@ -0,0 +1,109 @@
|
||||
<!--
|
||||
PR title: Conventional Commits — type(scope): description
|
||||
e.g. fix(mv): bring header and completions up to repo conventions
|
||||
Lowercase after the colon, imperative mood, no trailing period.
|
||||
Append `!` after the scope for a breaking change: feat(logging)!: ...
|
||||
Types in use: feat, fix, docs, test, chore, perf, refactor, style.
|
||||
See CONTRIBUTING.md § Commit Conventions.
|
||||
|
||||
Still writing code? Prefix the title with `WIP:` —
|
||||
WIP: feat(media): add play-media fzf picker
|
||||
Gitea recognizes the prefix, marks the PR a draft, and blocks merging
|
||||
until you remove it. The prefix goes before the whole conventional
|
||||
subject, and comes off when the branch is complete.
|
||||
|
||||
`WIP:` means MORE CHANGES ARE COMING. It is not for a finished branch
|
||||
waiting on verification — that is what an unchecked box in Verification
|
||||
already signals. A complete branch with open checkboxes is a normal PR,
|
||||
not a WIP.
|
||||
|
||||
Keep the two headings below, in this order, and delete these comments.
|
||||
Optional sections may be added between them (see the note above
|
||||
"Verification").
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!--
|
||||
What changed and why. A short prose paragraph, 2-5 bullets, or a
|
||||
paragraph followed by bullets — whichever fits the change.
|
||||
|
||||
- Name concrete paths and identifiers in backticks (`functions/mv.fish`,
|
||||
`$__fish_config_op_autoexec`), not vague descriptions.
|
||||
- Lead a bullet with a **bold phrase** when it covers a distinct file or
|
||||
behavior, so the list scans at a glance.
|
||||
- Explain the reasoning, not just the diff — a reader should understand
|
||||
why this approach over the obvious alternative.
|
||||
- Say so explicitly when there is no behavioral change, when a file is
|
||||
generated by the docs pipeline, or when the change is scoped to the
|
||||
site build only.
|
||||
|
||||
If this PR resolves a tracked issue, close it with a trailing line at the
|
||||
end of this section — not at the very bottom, since Verification is
|
||||
always last:
|
||||
|
||||
Closes #42
|
||||
|
||||
Gitea and GitHub both auto-close the issue on merge. `Fixes #N` and
|
||||
`Resolves #N` work identically; pick whichever reads correctly. Use one
|
||||
line per issue (`Closes #42, closes #43` — the keyword must be repeated,
|
||||
a bare `#43` after a comma is only a link and will NOT close). For an
|
||||
issue that is related but should stay open, reference it without a
|
||||
keyword: `Refs #42`. Omit all of this entirely when no issue is involved
|
||||
— do not leave an empty `Closes #` behind.
|
||||
-->
|
||||
|
||||
<!--
|
||||
OPTIONAL SECTIONS — add any `##` heading here that the change actually
|
||||
needs, and skip them entirely for a straightforward one. Used in this
|
||||
repo, roughly in order of frequency:
|
||||
|
||||
## Root cause — for a bug fix, what was actually wrong
|
||||
## Why — motivation, when it isn't obvious
|
||||
## How it works — mechanism, for a non-trivial new feature
|
||||
## Behavior — user-visible behavior before/after
|
||||
## Changes — a longer breakdown than Summary can carry
|
||||
## Docs — documentation touched by this change
|
||||
## Notes — caveats, follow-ups, deferred work
|
||||
## Scope note — what this deliberately does NOT cover
|
||||
## Opinionated guard (C1-C6)
|
||||
— which tier gates this, and behavior when off
|
||||
## ⚠️ Breaking Change — required whenever the title carries `!`;
|
||||
state the break and the migration path
|
||||
|
||||
A "Why not <alternative>?" heading is also common when a reviewer would
|
||||
reasonably ask why the obvious approach was rejected.
|
||||
-->
|
||||
|
||||
## Verification
|
||||
|
||||
<!--
|
||||
Every check this change needs, as a checkbox list. Always the last
|
||||
section.
|
||||
|
||||
A CHECKED box means verified — either it ran programmatically (test
|
||||
suite, linter, docs verifier, CI) or the author performed it by hand and
|
||||
confirmed the result. Check these off before opening the PR.
|
||||
|
||||
An UNCHECKED box is an outstanding manual check the reviewer still has to
|
||||
perform. Leave anything you could not verify yourself unchecked rather
|
||||
than dropping it, so it stays visible.
|
||||
|
||||
**This list is the merge gate: the PR does not merge until every box is
|
||||
checked.** So only put things here that can actually be resolved — a
|
||||
check nobody is able to run blocks the PR forever. Genuinely unverifiable
|
||||
caveats, assumptions, and known limitations belong in a `## Notes`
|
||||
section instead, where they inform the review without gating it.
|
||||
|
||||
- One check per line, imperative, with the exact command in backticks
|
||||
and the expected result stated.
|
||||
- Cover the regression path, not just the happy path: the old behavior
|
||||
still working, the opinionated toggle disabled, the error branch.
|
||||
- Include the repo's standing gates when the change touches what they
|
||||
guard: `fish -n <file>`, `fish_indent`, `python3 docs/verify-manual.py`,
|
||||
`python3 docs/build-manual.py --site`, `fish tests/run-tests.fish`.
|
||||
- Reset any universal variable you set during a check.
|
||||
-->
|
||||
|
||||
- [ ]
|
||||
- [ ]
|
||||
@@ -29,8 +29,15 @@ on:
|
||||
- build-docs
|
||||
|
||||
jobs:
|
||||
# This workflow file is mirrored to GitHub as-is, but the runner label
|
||||
# below (racknerd-mini) only exists on the Gitea instance -- on GitHub
|
||||
# the job just sits queued forever with no matching runner, so the
|
||||
# mirror never gets a completed status. Gate the real jobs to Gitea and
|
||||
# let the github-mirror job below stand in on GitHub instead.
|
||||
test:
|
||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all' || github.event.inputs.job == 'test'
|
||||
if: |
|
||||
github.server_url != 'https://github.com' &&
|
||||
(github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all' || github.event.inputs.job == 'test')
|
||||
runs-on: racknerd-mini
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -52,6 +59,7 @@ jobs:
|
||||
build-docs:
|
||||
needs: test
|
||||
if: |
|
||||
github.server_url != 'https://github.com' &&
|
||||
always() &&
|
||||
(github.event.inputs.job == 'build-docs' ||
|
||||
((github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all') &&
|
||||
@@ -119,10 +127,43 @@ jobs:
|
||||
--commit-dirty=true
|
||||
|
||||
- name: Commit generated docs
|
||||
env:
|
||||
BOT_GPG_KEY: ${{ secrets.CI_GPG_PRIVATE_KEY }}
|
||||
run: |
|
||||
git config user.name "Gitea Actions"
|
||||
git config user.email "actions@gitea"
|
||||
# actions@gitea was never a verified email on any account, so
|
||||
# these commits could never show as signed/verified regardless
|
||||
# of server config. fishconfig-bot is a dedicated Gitea account
|
||||
# (verified email, no login) that owns this key; the private
|
||||
# key lives only in the CI_GPG_PRIVATE_KEY repo secret.
|
||||
#
|
||||
# Secret is base64-encoded: a raw multi-line armored key piped
|
||||
# through `echo "$VAR" | gpg --import` came out CRC-corrupted
|
||||
# ("Invalid keyring") the first time this ran -- something in
|
||||
# the secret/env round-trip mangles embedded newlines. Base64
|
||||
# collapses it to one line immune to that.
|
||||
export GNUPGHOME="$(mktemp -d)"
|
||||
chmod 700 "$GNUPGHOME"
|
||||
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 --import
|
||||
git config user.name "Gitea Actions Bot"
|
||||
git config user.email "fishconfig-bot@git.rootiest.dev"
|
||||
git config user.signingkey CAA082C2F3467E1F7217AD492075C120312D23F4
|
||||
git config commit.gpgsign true
|
||||
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"
|
||||
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
|
||||
# self-hosted runner that only exists on the Gitea instance.
|
||||
github-mirror:
|
||||
if: github.server_url == 'https://github.com'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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/build-docs results."
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Sync labels to mirror
|
||||
|
||||
# Labels do not travel with a mirror push -- mirroring copies files, not
|
||||
# repository settings -- but they matter on the GitHub side anyway, because
|
||||
# GitHub reads the same .github/ISSUE_TEMPLATE/ files and silently drops a
|
||||
# labels: entry naming a label it does not have. Gitea is the source of
|
||||
# truth; this makes the mirror match.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 06:00 UTC daily. Label churn is rare, so a slower cadence than this
|
||||
# would leave the mirror wrong for most of a working day after an edit.
|
||||
- cron: "0 6 * * *"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
# Exercise the sync as soon as its own logic changes, rather than
|
||||
# waiting for the next scheduled run to find out it is broken.
|
||||
- "scripts/sync-labels.py"
|
||||
- ".github/workflows/sync-labels.yml"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: "Report the plan without changing anything"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
sync-labels:
|
||||
# This file is mirrored to GitHub as-is. The runner label below only
|
||||
# exists on the Gitea instance, so on GitHub the job would sit queued
|
||||
# forever against a runner that will never pick it up -- the same
|
||||
# problem the github-mirror stand-in in ci.yml exists to solve. A
|
||||
# skipped job costs nothing and produces no stuck status.
|
||||
if: github.server_url != 'https://github.com'
|
||||
runs-on: racknerd-mini
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Install Python
|
||||
run: |
|
||||
sudo apt-get -o Acquire::Retries=3 update -qq
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install \
|
||||
--no-install-recommends -y python3
|
||||
|
||||
# Cheap, offline, and no token needed. Catches a broken diff before
|
||||
# anything is allowed to mutate labels on the mirror.
|
||||
- name: Check the diff logic
|
||||
run: python3 scripts/sync-labels.py --self-test
|
||||
|
||||
- name: Sync labels
|
||||
env:
|
||||
GH_MIRROR_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$GH_MIRROR_TOKEN" ]; then
|
||||
echo "::error::GH_MIRROR_TOKEN is not set in this repository's Actions secrets."
|
||||
echo "Create a fine-grained GitHub token scoped to rootiest/fish-config with"
|
||||
echo "Issues: read and write, plus Pull requests: read, and add it as"
|
||||
echo "GH_MIRROR_TOKEN under Settings -> Actions -> Secrets."
|
||||
exit 1
|
||||
fi
|
||||
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
||||
python3 scripts/sync-labels.py --dry-run
|
||||
else
|
||||
python3 scripts/sync-labels.py
|
||||
fi
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# GitIgnore for Fish-config
|
||||
|
||||
# id: gitig-boilerplate-7f0528027e23f210196ae4583c408e5b
|
||||
# id: gitig-boilerplate-a27b89403ff9f0bb3340ede87b42f93e
|
||||
# ╭──────────────────────────────────────────────────────────╮
|
||||
# │ GitIgnore Boilerplate Template │
|
||||
# ╰──────────────────────────────────────────────────────────╯
|
||||
@@ -93,8 +93,9 @@ functions/sponge_*.fish
|
||||
[Aa][nN][Tt][Ii][Gg][Rr][Aa][Vv][Ii][Tt][Yy].[Mm][Dd]
|
||||
.[Aa][Nn][Tt][Ii][Gg][Rr][Aa][Vv]*
|
||||
|
||||
# Matches AGENTS.md, .remember, etc.
|
||||
# Matches AGENTS.md, .agents/ (agy), .remember, etc.
|
||||
[Aa][Gg][Ee][Nn][Tt][Ss].[Mm][Dd]
|
||||
.[Aa][Gg][Ee][Nn][Tt][Ss]*
|
||||
.[Rr][Ee][Mm][Ee][Mm][Bb][Ee][Rr]
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
|
||||
+291
-5
@@ -9,7 +9,9 @@ treat it as a living document, not a final word.
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Issues](#issues)
|
||||
- [Branching & Pull Requests](#branching--pull-requests)
|
||||
- [Labels](#labels)
|
||||
- [Commit Conventions](#commit-conventions)
|
||||
- [Fish Coding Standards](#fish-coding-standards)
|
||||
- [Opinionated Components](#opinionated-components)
|
||||
@@ -34,6 +36,79 @@ If you're touching anything under `docs/manual/`, you'll also want `pandoc`,
|
||||
(see [Documentation Pipeline](#documentation-pipeline)) — otherwise CI will
|
||||
catch problems on push.
|
||||
|
||||
## Issues
|
||||
|
||||
Issues live on the Gitea repo. Three templates cover the common cases, each
|
||||
pre-applying its `Kind/` label; blank issues stay enabled for everything else
|
||||
— a chore, a refactor, a question, a tracking issue.
|
||||
|
||||
| Template | Format | Use it for | Applies |
|
||||
|---|---|---|---|
|
||||
| **Bug report** | web form | Something is broken or behaves unexpectedly | `Kind/Bug` |
|
||||
| **Feature or enhancement request** | markdown | New functionality, or an improvement to what exists | `Kind/Feature` |
|
||||
| **Documentation issue** | markdown | The manual, man page, `config-help`, or docs site is wrong, missing, or unclear | `Kind/Documentation` |
|
||||
|
||||
They live in `.github/ISSUE_TEMPLATE/`, next to the PR template, so the
|
||||
GitHub mirror offers the same set. The bug report is a Gitea *issue form* —
|
||||
a real web form with required fields — because a bug report missing its
|
||||
version, reproduction, or full error text can't be acted on, and a form
|
||||
refuses to submit without them. The other two are markdown templates in the
|
||||
same comment-guided style as `.github/PULL_REQUEST_TEMPLATE.md`, since what
|
||||
they ask for is open-ended prose that structure would only get in the way of.
|
||||
|
||||
GitHub reads these same files on the mirror, and its schema differs from
|
||||
Gitea's in two places, so both are pinned to the spelling that works on both
|
||||
and each file says so in a comment: the chooser config must be `config.yml`
|
||||
(GitHub ignores `config.yaml`), and `bug.yml` declares `description:` rather
|
||||
than `about:` (GitHub requires it; Gitea accepts it as an alias). The two
|
||||
markdown templates keep `about:`, which is correct for their format on both.
|
||||
|
||||
### Issue titles
|
||||
|
||||
**Issue titles are plain descriptions of the problem, not Conventional
|
||||
Commits subjects.**
|
||||
|
||||
```text
|
||||
mv clobbers a symlink when the target exists ← yes
|
||||
fix(mv): prompt before replacing an existing symlink ← no
|
||||
```
|
||||
|
||||
An issue states a problem; a commit states a change. The type and scope that
|
||||
`fix(mv):` would carry are already on the issue as its `Kind/` and `Area/`
|
||||
labels, and the conventional subject belongs on the PR that closes it, where
|
||||
it becomes the commit message. Writing the fix into the title also presumes
|
||||
one, which is the wrong end to start from for anything still being diagnosed.
|
||||
|
||||
### What an issue owes
|
||||
|
||||
- **A bug** needs a reproduction someone else can paste and run, starting
|
||||
from a fresh shell, plus the complete error output. A stale function
|
||||
definition in a long-lived session is the most common false alarm, so
|
||||
confirm it survives `exec fish` first. `Status/Need More Info` is where
|
||||
reports without a reproduction end up.
|
||||
- **A feature** needs `## Acceptance criteria` — the checkbox list of what
|
||||
must be true for the issue to close. It is the issue-side counterpart to a
|
||||
PR's `## Verification`: a definition of done agreed before the work starts
|
||||
rather than argued about after, and the PR's checks usually grow out of it.
|
||||
- **A docs issue** needs to name the `docs/manual/**` source, not just the
|
||||
page where the problem showed up. `docs/fish-config.md` and
|
||||
`docs/fish-config.1` are generated, and a fix applied there is overwritten
|
||||
by the next CI run — see [Documentation
|
||||
Pipeline](#documentation-pipeline).
|
||||
|
||||
### Triage
|
||||
|
||||
Reporters aren't expected to label anything. Contributors without push access
|
||||
can't, and the templates apply the `Kind/` label by themselves; the rest is
|
||||
the maintainer's job when the issue is triaged — add the `Area/` label (the
|
||||
bug form's **Area** dropdown is how a reporter tells you, since no forge can
|
||||
map a form field to a label), set a `Priority/` if it isn't ordinary, and
|
||||
apply `Reviewed/Confirmed` once a bug actually reproduces. See
|
||||
[Labels](#labels).
|
||||
|
||||
When a PR resolves an issue it closes it with a trailing `Closes #N` line —
|
||||
see [Pull request descriptions](#pull-request-descriptions).
|
||||
|
||||
## Branching & Pull Requests
|
||||
|
||||
**If you don't have push access to this repo**, fork it and open your PR
|
||||
@@ -48,6 +123,22 @@ assumes you *do* have push access (maintainers, regular contributors).
|
||||
branching doesn't touch the working tree.)
|
||||
- **Merge target is `main`, via PR.** Contributors open the PR; the repo
|
||||
owner merges it. Don't merge your own PR.
|
||||
- **Label every PR.** At minimum one `Kind/` and one `Area/`, same as an
|
||||
issue — see [Labels](#labels). If you can't set labels, say what the
|
||||
change is in the description and a maintainer applies them.
|
||||
- **Don't merge until the `## Verification` checklist is fully checked.**
|
||||
Unchecked boxes are outstanding manual checks, not decoration. See
|
||||
[Pull request descriptions](#pull-request-descriptions) below.
|
||||
- **Prefix in-progress PRs with `WIP:`.** If the branch still has code
|
||||
changes coming, open it as `WIP: type(scope): description`. Gitea
|
||||
recognizes the prefix, flags the PR as a draft, and refuses to merge it
|
||||
until the prefix is removed; drop it once the branch is complete.
|
||||
|
||||
`WIP:` signals **more changes are coming** — not "done but unverified".
|
||||
A finished branch waiting on manual checks is an ordinary PR whose
|
||||
`## Verification` boxes aren't all ticked yet; that's already the merge
|
||||
gate above and doesn't need the prefix. The two are independent: a PR
|
||||
can be WIP with everything ticked, or complete with checks outstanding.
|
||||
- **Keep feature branches focused.** If you stumble onto something unrelated
|
||||
to your current task while working (a pre-existing bug, a stray cleanup),
|
||||
don't fold it into the same commit or PR. Handle it with one of these,
|
||||
@@ -64,6 +155,183 @@ 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.
|
||||
|
||||
### Pull request descriptions
|
||||
|
||||
Fill in `.github/PULL_REQUEST_TEMPLATE.md` — Gitea pre-loads it into the
|
||||
description box when you open a PR. Every PR carries, in this order:
|
||||
|
||||
- **`## Summary`** — what changed and why, as a short paragraph, 2-5
|
||||
bullets, or both. Name concrete paths and identifiers in backticks, and
|
||||
explain the reasoning rather than restating the diff.
|
||||
- **Optional `##` sections** — add what the change actually needs
|
||||
(`Root cause`, `Why`, `How it works`, `Behavior`, `Docs`, `Notes`,
|
||||
`Scope note`, `Opinionated guard (C1-C6)`), and skip them entirely for a
|
||||
straightforward change. A breaking change (title ending in `!` before
|
||||
the colon) must include `## ⚠️ Breaking Change` with the migration path.
|
||||
- **`## Verification`** — always last. Every check this change needs, as a
|
||||
checkbox list, each with the exact command and its expected result.
|
||||
A **checked** box means verified, whether programmatically (test suite,
|
||||
linter, docs verifier, CI) or by hand; check those off before opening the
|
||||
PR. An **unchecked** box is an outstanding manual check the reviewer
|
||||
still has to perform — leave anything you couldn't verify yourself
|
||||
unchecked rather than dropping it.
|
||||
|
||||
**This list is the merge gate: a PR isn't merged until every box is
|
||||
checked.** Only list checks that can actually be resolved — one nobody
|
||||
can run blocks the PR indefinitely. Put genuinely unverifiable caveats,
|
||||
assumptions, and known limitations in `## Notes` instead, where they
|
||||
inform the review without gating it.
|
||||
|
||||
When a PR resolves a tracked issue, close it with a trailing `Closes #42`
|
||||
line at the end of `## Summary` — not at the very bottom of the body, since
|
||||
`## Verification` is always last. `Fixes #N` and `Resolves #N` behave
|
||||
identically. Repeat the keyword for each issue (`Closes #42, closes #43`); a
|
||||
bare `#43` is only a link and won't close anything. To point at a related
|
||||
issue that should stay open, drop the keyword and use `Refs #42`. Leave the
|
||||
line out entirely when no issue is involved.
|
||||
|
||||
## Labels
|
||||
|
||||
**Every issue and every pull request carries exactly one `Kind/` label and at
|
||||
least one `Area/` label.** Everything else is optional, and most of it is
|
||||
applied by a maintainer at triage rather than by whoever opened the thing.
|
||||
|
||||
Labels are scoped: the `Group/Name` form renders as a two-tone chip in Gitea,
|
||||
and for the three *exclusive* groups below Gitea enforces one-at-a-time by
|
||||
swapping the old label out when you apply a new one.
|
||||
|
||||
### `Kind/` — what this is
|
||||
|
||||
Required, and by convention exactly one. Gitea doesn't enforce one-of here,
|
||||
so pick the dominant character of the change instead of stacking two.
|
||||
|
||||
| Label | For |
|
||||
|---|---|
|
||||
| `Kind/Bug` | Something is not working |
|
||||
| `Kind/Feature` | New functionality |
|
||||
| `Kind/Enhancement` | Improves functionality that already exists |
|
||||
| `Kind/Documentation` | Documentation changes |
|
||||
| `Kind/Testing` | The test suite itself |
|
||||
| `Kind/Refactor` | Restructures code without changing behavior |
|
||||
| `Kind/Chore` | Tooling, dependencies, housekeeping |
|
||||
| `Kind/Performance` | Makes existing behavior faster or lighter |
|
||||
| `Kind/Security` | A security issue |
|
||||
|
||||
These deliberately mirror the Conventional Commits types in [Commit
|
||||
Conventions](#commit-conventions), so a PR's label and its title agree:
|
||||
`fix` → `Kind/Bug`, `feat` → `Kind/Feature` or `Kind/Enhancement`, `docs` →
|
||||
`Kind/Documentation`, `test` → `Kind/Testing`, `refactor` →
|
||||
`Kind/Refactor`, `chore` → `Kind/Chore`, `perf` → `Kind/Performance`.
|
||||
|
||||
### `Area/` — what it touches
|
||||
|
||||
Required, and non-exclusive on purpose: a change that adds a function, its
|
||||
completions, and a manual entry gets all three.
|
||||
|
||||
| Label | Covers |
|
||||
|---|---|
|
||||
| `Area/Functions` | `functions/` |
|
||||
| `Area/Completions` | `completions/` |
|
||||
| `Area/Config` | `config.fish`, `conf.d/` — startup and environment |
|
||||
| `Area/Docs` | `docs/manual/` and the generated manual, man page, and site |
|
||||
| `Area/Tests` | `tests/` |
|
||||
| `Area/CI` | `.github/workflows/` and repository automation |
|
||||
| `Area/Integrations` | `integrations/` |
|
||||
| `Area/Prompt & Theme` | `themes/` and prompt appearance |
|
||||
| `Area/Components` | The opinionated-component system (C1-C6) |
|
||||
| `Area/Scripts` | `scripts/` |
|
||||
|
||||
`Area/` is what makes the tracker searchable: it answers "what's still
|
||||
outstanding in the docs pipeline?" in a way `Kind/` never can. Two edges
|
||||
worth naming — `Area/Docs` covers the documentation *and its pipeline*, so
|
||||
`README.md` and this file count even though they sit outside `docs/`; and
|
||||
`Area/Components` is for the C1-C6 machinery itself, not for every function
|
||||
that happens to carry a `# COMPONENT` header.
|
||||
|
||||
### `Compat/Breaking`
|
||||
|
||||
Applied to **any PR whose title carries `!` before the colon**, and to any
|
||||
issue proposing a change that would. It travels with the `## ⚠️ Breaking
|
||||
Change` section that such a PR must already include — see [Pull request
|
||||
descriptions](#pull-request-descriptions).
|
||||
|
||||
### `Priority/` — exclusive, maintainer-applied
|
||||
|
||||
`Priority/Critical`, `Priority/High`, `Priority/Medium`, `Priority/Low`.
|
||||
|
||||
**No priority label means ordinary priority.** Labeling everything defeats
|
||||
the point, so leave it off unless the item is genuinely more or less urgent
|
||||
than the rest of the queue.
|
||||
|
||||
### `Reviewed/` — exclusive, maintainer-applied
|
||||
|
||||
`Reviewed/Confirmed` goes on a bug that has actually been reproduced —
|
||||
that's the signal separating a report from a known defect.
|
||||
`Reviewed/Duplicate`, `Reviewed/Invalid`, and `Reviewed/Won't Fix` accompany
|
||||
closing an issue, always with a comment saying why; a close with only a
|
||||
label on it is not an explanation.
|
||||
|
||||
### `Status/` — exclusive, maintainer-applied
|
||||
|
||||
`Status/Blocked`, `Status/Need More Info`, `Status/Abandoned`. These describe
|
||||
the item's current state, so remove one as soon as it stops being true — a
|
||||
stale `Status/Need More Info` on an issue that got its answer is worse than
|
||||
no label, because it reads as still waiting.
|
||||
|
||||
### `good first issue` and `help wanted`
|
||||
|
||||
Invitations to contributors, applied by a maintainer. Both are deliberately
|
||||
**unscoped**: they'd be a natural fit under `Status/`, but that group is
|
||||
exclusive, and an issue is quite often both blocked on something *and* open
|
||||
for someone to pick up. Keeping them outside the group lets them coexist
|
||||
with a real status.
|
||||
|
||||
Use `good first issue` for work that is genuinely self-contained — a clear
|
||||
acceptance criterion, one or two files, no need to understand the
|
||||
opinionated-component system first.
|
||||
|
||||
### The GitHub mirror
|
||||
|
||||
The repo is mirrored to
|
||||
[github.com/rootiest/fish-config](https://github.com/rootiest/fish-config),
|
||||
and **the mirror carries the same labels, by the same names**. That isn't
|
||||
cosmetic: GitHub reads the same `.github/ISSUE_TEMPLATE/` files, and a
|
||||
`labels:` entry naming a label that doesn't exist on that side is silently
|
||||
dropped rather than reported.
|
||||
|
||||
Mirroring copies files, not repository settings, so labels don't travel with
|
||||
a push. **`.github/workflows/sync-labels.yml` closes that gap**: it runs
|
||||
`scripts/sync-labels.py` on a daily schedule, and again whenever the script
|
||||
itself changes, to make GitHub match Gitea. Manage labels here, in the Gitea
|
||||
UI, and the mirror catches up within a day — or dispatch the workflow by
|
||||
hand for it to happen now. Nothing needs doing on the GitHub side.
|
||||
|
||||
The sync creates what's missing and corrects color or description drift,
|
||||
and it deletes an extra label on the mirror **only when no issue or PR there
|
||||
carries it**; one that's in use is reported with its count and left for a
|
||||
human to decide about. Run the script with `--dry-run` to see the plan
|
||||
without changing anything, or `--self-test` to check its diff logic offline
|
||||
— both are useful before editing it. Because labels are matched by name,
|
||||
renaming one on Gitea reads as a delete plus a create: the new name appears
|
||||
on the mirror, and the old one is pruned only if it's unused.
|
||||
|
||||
The workflow needs a GitHub token in this repo's Actions secrets as
|
||||
`GH_MIRROR_TOKEN`, scoped to the mirror with **Issues: read and write**
|
||||
(GitHub files labels under Issues) and **Pull requests: read** (so the
|
||||
in-use check sees labels on PRs). The job fails with an explicit message if
|
||||
it's missing rather than quietly doing nothing.
|
||||
|
||||
One behavioral difference to keep in mind: **GitHub has no exclusive
|
||||
labels.** Gitea enforces one-at-a-time on `Priority/`, `Reviewed/`, and
|
||||
`Status/` by swapping the old label out; on the mirror those are ordinary
|
||||
labels and nothing stops two of a group coexisting, so there the one-of rule
|
||||
holds by convention alone.
|
||||
|
||||
Issues and pull requests belong on the canonical Gitea repo — the template
|
||||
chooser links there first, on both sides. The mirror's tracker stays open so
|
||||
that a report which lands there anyway isn't lost, not because it's a second
|
||||
supported front door.
|
||||
|
||||
## Commit Conventions
|
||||
|
||||
Commit subjects follow [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
@@ -212,11 +480,9 @@ variable, or otherwise falls under an existing category, tag it with a
|
||||
`# COMPONENT` header (`<category>/<subcategory>`, e.g.
|
||||
`overrides/key-bindings`) and guard it with `__fish_config_op_enabled`. See
|
||||
the [README's Minimal Mode section](README.md#minimal-mode) for the full
|
||||
category list and toggle semantics, and
|
||||
`AGENTS/specs/2026-08-17-opinionated-component-subcategories-design.md` for
|
||||
the underlying design. Most new functions are *not* opinionated components
|
||||
— only tag something if it fits an existing category; this isn't something
|
||||
to force.
|
||||
category list and toggle semantics. Most new functions are *not*
|
||||
opinionated components — only tag something if it fits an existing
|
||||
category; this isn't something to force.
|
||||
|
||||
## Documentation Pipeline
|
||||
|
||||
@@ -239,6 +505,26 @@ python3 docs/verify-manual.py
|
||||
CI runs the same verification and regenerates the site/man page — a broken
|
||||
manual won't get published, but running it locally saves a round trip.
|
||||
|
||||
Write doc-headers as plain text — no backticks. `-a/--all`,
|
||||
`__fish_config_op_aliases` and `~/.config/fish/config.fish` are typed
|
||||
bare, because the header is also read as-is by `config-help` and by
|
||||
anyone opening the file. `docs/codespans.py` adds the inline code spans
|
||||
when it renders, so the SSOT never carries them; see
|
||||
`docs/site/README.md` for which shapes it recognises. That pass runs for
|
||||
every output — the site, the man page and `config-help` — so a token is
|
||||
typeset the same way wherever it is read.
|
||||
|
||||
Two rules apply to backticks you write under `docs/manual/` as well:
|
||||
|
||||
- **Never inside an indented block.** A four-space block is verbatim in
|
||||
every renderer, so a backtick there is a literal character on the page
|
||||
rather than markup.
|
||||
- **Never wrapped across a line break.** Markdown accepts a span split
|
||||
over two lines, but `config-help` pairs backticks one line at a time
|
||||
and would show the halves literally. Reflow the sentence instead.
|
||||
|
||||
`docs/verify-manual.py` enforces both.
|
||||
|
||||
## Testing
|
||||
|
||||
```fish
|
||||
|
||||
@@ -146,9 +146,12 @@ Contributing to the docs? There are two sources, split by content type:
|
||||
above each function in `functions/*.fish`. Edit the function; the entry
|
||||
and its site page are generated from the header.
|
||||
- **Everything else** lives under `docs/manual/**`.
|
||||
- **Testing, Contributing, Attribution, and License** are pulled straight
|
||||
from this README (the sections below) rather than authored twice — edit
|
||||
them here and the manual, man page, and site all pick up the change.
|
||||
|
||||
Never edit the generated `docs/fish-config.md` — it's rebuilt from both
|
||||
sources and any hand-edits are discarded.
|
||||
Never edit the generated `docs/fish-config.md` — it's rebuilt from all
|
||||
three sources and any hand-edits are discarded.
|
||||
|
||||
To browse the docs from the terminal:
|
||||
|
||||
@@ -183,6 +186,8 @@ Then open a new Fish shell — Fisher will be installed automatically on first l
|
||||
|
||||
A [chezmoi](https://www.chezmoi.io/) dotfile manager is also configured — secrets are kept in a private overlay directory (see [Personalization](#personalization)) and excluded from version control.
|
||||
|
||||
**External requirements.** Everything degrades gracefully if a tool is missing, with two exceptions worth calling out. `python3` is required by the documentation pipeline and by `config-settings`, whose TUI is drawn with the stdlib `curses` module. That module ships with CPython on Arch, Fedora and a full Debian/Ubuntu `python3`; `python3-minimal` alone does **not** carry `_curses`, so install the complete `python3` package there. `config-settings` checks for both and tells you which is missing rather than failing inside the renderer.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `config.fish` ends with a `return` sentinel guard. Any lines appended **after** it by a tool's setup command will silently have no effect. Many tools (starship, zoxide, mise, etc.) offer a setup command that appends an `init | source` line to your `config.fish` — all integrations are managed through `conf.d/` files instead. If you add a new tool and its shell integration appears to do nothing, check whether its setup command appended an init line to the bottom of `config.fish` and create a `conf.d/<tool>.fish` file for it instead.
|
||||
|
||||
@@ -285,7 +290,7 @@ To opt out, set `__fish_user_dots_symlink` to a falsy value (or toggle **Dots li
|
||||
|
||||
Everything opinionated in this config — command shadows, startup side-effects, key and environment overrides, terminal integrations, and the first-run greeting — is active by default but can be switched off. Logging (C5) is the exception: it is **opt-in**, off until you explicitly enable it.
|
||||
|
||||
> **The easy way — `config-settings`:** Run `config-settings` for an interactive TUI that manages settings across four pages — **Universal** and **Session** (the opinionated category toggles below, persistent or per-shell), **Sponge** (history-scrubbing: delay, exit codes, purge-on-exit, and extra sensitive variable names), and **Paths** (scrollback log dir, max files, and the user-dots path) — without typing a single variable name. Navigate with the arrow keys (or `h`/`j`/`k`/`l`); toggle rows step OFF ← DEFAULT → ON, value rows edit inline with `Enter` and clear with `←`. `Tab`/`Shift-Tab` cycle pages and `q` quits. Changes apply instantly. The panel auto-sizes to your terminal width (four tiers from 52- to 78-wide with a 6-column margin), centers itself horizontally, and redraws within ~0.3 s of a resize.
|
||||
> **The easy way — `config-settings`:** Run `config-settings` for an interactive TUI that manages settings across four pages — **Universal** and **Session** (the opinionated category toggles below, persistent or per-shell), **Sponge** (history-scrubbing: delay, exit codes, purge-on-exit, and extra sensitive variable names), and **Paths** (scrollback log dir, max files, and the user-dots path) — without typing a single variable name. Navigate with the arrow keys (or `h`/`j`/`k`/`l`); toggle rows step OFF ← DEFAULT → ON, value rows edit inline with `Enter` and reset with `←`. `Enter` on a category opens its sub-categories, `/` filters the page — reaching into sub-categories too, listed as `Category › Sub` — `?` opens a help overlay, `Tab`/`Shift-Tab` cycle pages, and `q` applies your edits and quits. Edits are collected as you make them and written in one batch on exit; the status bar shows the pending count. The panel is drawn with Python's stdlib `curses`, so it resizes with the terminal and never flickers — see [Installation](#installation) for the `python3` requirement.
|
||||
|
||||
If you'd rather set them by hand, each category is controlled by a universal variable. Six category toggles and one master switch are available:
|
||||
|
||||
@@ -293,7 +298,7 @@ If you'd rather set them by hand, each category is controlled by a universal var
|
||||
|---|---|
|
||||
| `__fish_config_op_aliases` | Command shadows: `ls`→eza, `cat`→bat, `cd`→zoxide, `rm`→trash, `top`→btop, `edit`→multi-editor launcher, and friends; `grep`/`cp`/`mv`/`wget` flag injection |
|
||||
| `__fish_config_op_autoexec` | Startup side-effects: Fisher bootstrap, theme apply, `paru`/`yay` wrapper generation, auto venv activation, WakaTime hook, auto-pull background fast-forward |
|
||||
| `__fish_config_op_overrides` | Vi mode, `exit`→`smart_exit`, `$PAGER`/`$MANPAGER`/`$CDPATH`/`XDG`/`PATH`, bang-bang history expansion, autopair, puffer, Starship prompt, theme colors |
|
||||
| `__fish_config_op_overrides` | Vi mode, `exit`→`smart_exit`, `$PAGER`/`$MANPAGER`/`$CDPATH`/`XDG`/`PATH`, `$DO_NOT_TRACK`/`$DISABLE_TELEMETRY`, bang-bang history expansion, autopair, puffer, Starship prompt, theme colors |
|
||||
| `__fish_config_op_integrations` | Kitty/WezTerm window abbreviations, `done` notifications, `spwin`/`tab`/`split`, `hist`, `logs`, `upgrade`, WakaTime |
|
||||
| `__fish_config_op_logging` | **Opt-in — off unless explicitly enabled.** Scrollback capture on exit, tmux `pipe-pane` pane logging, zellij `dump-screen` capture on exit, `paru`/`yay` AUR log wrappers, Kitty watcher capture (sentinel-file coordinated) |
|
||||
| `__fish_config_op_greeting` | Per-session `fish_greeting` (suppresses distro greetings such as CachyOS fastfetch by overriding with an empty function); first-run welcome banner |
|
||||
@@ -326,8 +331,10 @@ Each category further sub-divides into two to six sub-categories with
|
||||
their own `__fish_config_op_<category>_<subcategory>` toggles (e.g.
|
||||
`__fish_config_op_aliases_filesystem`), following the exact same
|
||||
truthy/falsy/unset cascade one level deeper. Run `config-settings` and
|
||||
press Enter on a category row to browse and toggle its sub-categories, or
|
||||
see the [Components Reference](https://fish.rootiest.fyi/08-components-reference/)
|
||||
press Enter on a category row to browse and toggle its sub-categories —
|
||||
or press `/` and type, which searches sub-categories across every
|
||||
category at once and lists the hits as `Category › Sub`. Or see the
|
||||
[Components Reference](https://fish.rootiest.fyi/08-components-reference/)
|
||||
for the full sub-category list per category.
|
||||
|
||||
---
|
||||
@@ -348,6 +355,14 @@ Interested in contributing? See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the
|
||||
branching/PR workflow, commit conventions, fish coding standards, and the
|
||||
docs/testing pipeline this repo follows.
|
||||
|
||||
**Preferred forge:** [git.rootiest.dev/rootiest/fish-config](https://git.rootiest.dev/rootiest/fish-config)
|
||||
is the base repository. [github.com/rootiest/fish-config](https://github.com/rootiest/fish-config)
|
||||
is a push-mirror of it — identical content, but one-way and read-only from a
|
||||
contributor's perspective. Branches, forks, and merges made on the GitHub
|
||||
side aren't fed back upstream, so they risk being silently overwritten by
|
||||
the next mirror push. Until two-way sync exists, please fork, branch, and
|
||||
open issues/PRs from the Gitea repository rather than the GitHub mirror.
|
||||
|
||||
---
|
||||
|
||||
## Attribution
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# Completions for agents-vault.
|
||||
|
||||
complete -c agents-vault -f
|
||||
complete -c agents-vault -s h -l help -d 'Show help message'
|
||||
complete -c agents-vault -l link -d "Ensure this project's memory link only"
|
||||
complete -c agents-vault -l push -d 'Commit and push to the vault remote'
|
||||
complete -c agents-vault -l restore -d 'Relink everything possible, report the rest'
|
||||
complete -c agents-vault -l status -d 'Show entries, link health, remote, orphans'
|
||||
# --adopt takes an existing vault slug, so offer the entries that are
|
||||
# actually there; the vault may not exist yet, in which case this is empty.
|
||||
# -A because a dot-led slug is legitimate (a relative-path remote keys as
|
||||
# ..-mirror), and an entry that cannot be completed reads as one that is
|
||||
# not there.
|
||||
complete -c agents-vault -l adopt -r -a '(command ls -1A (_agents_vault_dir)/projects 2>/dev/null)' -d 'Bind this project to an existing vault entry'
|
||||
complete -c agents-vault -l remote -r -d 'Set the vault remote URL'
|
||||
complete -c agents-vault -s v -l verbose -d 'Print all per-step output (default)'
|
||||
complete -c agents-vault -s q -l quiet -d 'Print one summary line only if changed'
|
||||
complete -c agents-vault -s s -l silent -d 'Suppress all output; errors only'
|
||||
+4
-5
@@ -3,7 +3,7 @@
|
||||
function __bd_debug
|
||||
set -l file "$BASH_COMP_DEBUG_FILE"
|
||||
if test -n "$file"
|
||||
echo "$argv" >> $file
|
||||
echo "$argv" >>$file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,7 +108,6 @@ function __bd_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __bd_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -215,18 +214,18 @@ end
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "bd"
|
||||
if type -q bd
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
complete --do-complete "bd " > /dev/null 2>&1
|
||||
complete --do-complete "bd " >/dev/null 2>&1
|
||||
end
|
||||
|
||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||
complete -c bd -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__bd_perform_completion_once_result global
|
||||
complete -c bd -n '__bd_clear_perform_completion_once_result'
|
||||
complete -c bd -n __bd_clear_perform_completion_once_result
|
||||
# The call to __bd_prepare_completions will setup __bd_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
|
||||
+22
-22
@@ -1,14 +1,14 @@
|
||||
# fish completion for copilot
|
||||
# Generated by `copilot completion fish`. Do not edit by hand.
|
||||
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'login' -d 'Authenticate with Copilot'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'help' -d 'Display help information'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'init' -d 'Initialize Copilot instructions'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'update' -d 'Download the latest version'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'version' -d 'Display version information'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'plugin' -d 'Manage plugins'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'mcp' -d 'Manage MCP servers'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'completion' -d 'Generate a shell completion script'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a login -d 'Authenticate with Copilot'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a help -d 'Display help information'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a init -d 'Initialize Copilot instructions'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a update -d 'Download the latest version'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a version -d 'Display version information'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a plugin -d 'Manage plugins'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a mcp -d 'Manage MCP servers'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a completion -d 'Generate a shell completion script'
|
||||
complete -c copilot -l version -s v -f -d 'show version information'
|
||||
complete -c copilot -l interactive -s i -r -d 'Start interactive mode and automatically execute this prompt'
|
||||
complete -c copilot -l prompt -s p -r -d 'Execute a prompt in non-interactive mode (exits after completion)'
|
||||
@@ -75,22 +75,22 @@ complete -c copilot -l acp -f -d 'Start as Agent Client Protocol server'
|
||||
complete -c copilot -l remote -f -d 'Enable remote control of your session from GitHub web and mobile'
|
||||
complete -c copilot -l no-remote -f -d 'Disable remote control of your session from GitHub web and mobile'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from login' -l host -r -d 'GitHub host URL (default: https://github.com)'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'install' -d 'Install a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'uninstall' -d 'Uninstall a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'update' -d 'Update a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'list' -d 'List installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'marketplace' -d 'Manage plugin marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a install -d 'Install a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a uninstall -d 'Uninstall a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a update -d 'Update a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a list -d 'List installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a marketplace -d 'Manage plugin marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l all -f -d 'Update all installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'add' -d 'Add a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'remove' -d 'Remove a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'list' -d 'List registered marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'browse' -d 'Browse plugins in a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'update' -d 'Update marketplace plugin catalogs'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a add -d 'Add a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a remove -d 'Remove a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a list -d 'List registered marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a browse -d 'Browse plugins in a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a update -d 'Update marketplace plugin catalogs'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace; and __fish_seen_subcommand_from remove' -l force -s f -f -d 'Force removal even if plugins are installed'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'list' -d 'List configured MCP servers'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'get' -d 'Show server details'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'add' -d 'Add an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'remove' -d 'Remove an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a list -d 'List configured MCP servers'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a get -d 'Show server details'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a add -d 'Add an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a remove -d 'Remove an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from list' -l json -f -d 'Output as JSON'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l json -f -d 'Output as JSON'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l show-secrets -f -d 'Show full environment variable and header values (masked by default)'
|
||||
|
||||
+60
-60
@@ -1,38 +1,38 @@
|
||||
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
|
||||
function __fish_deadbranch_global_optspecs
|
||||
string join \n h/help V/version
|
||||
string join \n h/help V/version
|
||||
end
|
||||
|
||||
function __fish_deadbranch_needs_command
|
||||
# Figure out if the current invocation already has a command.
|
||||
set -l cmd (commandline -opc)
|
||||
set -e cmd[1]
|
||||
argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null
|
||||
or return
|
||||
if set -q argv[1]
|
||||
# Also print the command, so this can be used to figure out what it is.
|
||||
echo $argv[1]
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
# Figure out if the current invocation already has a command.
|
||||
set -l cmd (commandline -opc)
|
||||
set -e cmd[1]
|
||||
argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null
|
||||
or return
|
||||
if set -q argv[1]
|
||||
# Also print the command, so this can be used to figure out what it is.
|
||||
echo $argv[1]
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function __fish_deadbranch_using_subcommand
|
||||
set -l cmd (__fish_deadbranch_needs_command)
|
||||
test -z "$cmd"
|
||||
and return 1
|
||||
contains -- $cmd[1] $argv
|
||||
set -l cmd (__fish_deadbranch_needs_command)
|
||||
test -z "$cmd"
|
||||
and return 1
|
||||
contains -- $cmd[1] $argv
|
||||
end
|
||||
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "list" -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "config" -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "backup" -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "stats" -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "completions" -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a list -d 'List stale branches'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a config -d 'Manage configuration'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a backup -d 'Manage backups'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a stats -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a completions -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -s d -l days -d 'Only show branches older than N days (default: from config or 30)' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l local -d 'Only show local branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l remote -d 'Only show remote branches'
|
||||
@@ -51,11 +51,11 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s h -l hel
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help'
|
||||
@@ -64,18 +64,18 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from edit" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l repo -d 'Show backups for a specific repository by name' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l current -d 'Only show backups for current repository'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
|
||||
@@ -94,28 +94,28 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s y -l yes -d 'Skip confirmation prompt'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s d -l days -d 'Treat branches older than N days as stale (default: from config or 30)' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "list" -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "config" -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "backup" -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "stats" -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "completions" -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a list -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a config -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a backup -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a stats -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a completions -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
|
||||
+4
-5
@@ -3,7 +3,7 @@
|
||||
function __ov_debug
|
||||
set -l file "$BASH_COMP_DEBUG_FILE"
|
||||
if test -n "$file"
|
||||
echo "$argv" >> $file
|
||||
echo "$argv" >>$file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -108,7 +108,6 @@ function __ov_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __ov_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -215,18 +214,18 @@ end
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "ov"
|
||||
if type -q ov
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
complete --do-complete "ov " > /dev/null 2>&1
|
||||
complete --do-complete "ov " >/dev/null 2>&1
|
||||
end
|
||||
|
||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||
complete -c ov -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__ov_perform_completion_once_result global
|
||||
complete -c ov -n '__ov_clear_perform_completion_once_result'
|
||||
complete -c ov -n __ov_clear_perform_completion_once_result
|
||||
# The call to __ov_prepare_completions will setup __ov_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
|
||||
@@ -21,7 +21,7 @@ end
|
||||
function __tailscale_debug
|
||||
set -l file "$BASH_COMP_DEBUG_FILE"
|
||||
if test -n "$file"
|
||||
echo "$argv" >> $file
|
||||
echo "$argv" >>$file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -125,7 +125,6 @@ function __tailscale_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __tailscale_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -228,22 +227,21 @@ function __tailscale_prepare_completions
|
||||
return 0
|
||||
end
|
||||
|
||||
# Since Fish completions are only loaded once the user triggers them, we trigger them ourselves
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "tailscale"
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
complete --do-complete "tailscale " > /dev/null 2>&1
|
||||
end
|
||||
# REMOVED (2026-09-07): Cobra's self-priming block —
|
||||
# if type -q "tailscale"; complete --do-complete "tailscale " >/dev/null 2>&1; end
|
||||
# It existed to force any pre-existing tailscale completions to load so the
|
||||
# `complete -c tailscale -e` below could erase them. From completions/ it has
|
||||
# no job: fish autoloads only the FIRST match on $fish_complete_path, and
|
||||
# $__fish_config_dir/completions precedes /usr/share/fish/vendor_completions.d,
|
||||
# so the vendor file is never sourced and there is nothing to erase. It also
|
||||
# executed the tailscale binary at startup. Verified: completion output is
|
||||
# byte-identical with and without it.
|
||||
|
||||
# Remove any pre-existing completions for the program since we will be handling all of them.
|
||||
complete -c tailscale -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__tailscale_perform_completion_once_result global
|
||||
complete -c tailscale -n '__tailscale_clear_perform_completion_once_result'
|
||||
complete -c tailscale -n __tailscale_clear_perform_completion_once_result
|
||||
# The call to __tailscale_prepare_completions will setup __tailscale_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
@@ -34,6 +34,7 @@ set -g __fish_config_op_registry_keys \
|
||||
"config:greeting-stamp" \
|
||||
"config:pager-editor-gpg" \
|
||||
"config:path-setup" \
|
||||
"config:privacy" \
|
||||
"config:vi-mode" \
|
||||
"done:" \
|
||||
"du:" \
|
||||
@@ -79,68 +80,68 @@ set -g __fish_config_op_registry_keys \
|
||||
"zoxide:"
|
||||
|
||||
set -g __fish_config_op_registry_values \
|
||||
"autoexec/venv" \
|
||||
"logging/terminal-capture" \
|
||||
"autoexec/sync" \
|
||||
"logging/multiplexer-capture" \
|
||||
"integrations/terminal-abbrs" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/dev-tools" \
|
||||
"autoexec/sync" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/shell-tools" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/dev-tools" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"overrides/environment" \
|
||||
"overrides/environment" \
|
||||
"overrides/key-bindings" \
|
||||
"greeting/greeting-message" \
|
||||
"overrides/environment" \
|
||||
"overrides/environment" \
|
||||
"overrides/key-bindings" \
|
||||
"integrations/notifications" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/dev-tools" \
|
||||
"autoexec/plugin-management" \
|
||||
"greeting/first-run" \
|
||||
"overrides/prompt" \
|
||||
"aliases/shell-tools" \
|
||||
"integrations/history-logs" \
|
||||
"overrides/key-bindings" \
|
||||
"logging/terminal-capture" \
|
||||
"logging/terminal-capture" \
|
||||
"aliases/shell-tools" \
|
||||
"integrations/history-logs" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/filesystem" \
|
||||
"autoexec/pkg-wrappers" \
|
||||
"logging/pkg-logs" \
|
||||
"aliases/network" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/search" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"logging/terminal-capture" \
|
||||
"integrations/window-mgmt" \
|
||||
"integrations/window-mgmt" \
|
||||
"aliases/network" \
|
||||
"overrides/prompt" \
|
||||
"integrations/window-mgmt" \
|
||||
"overrides/prompt" \
|
||||
"logging/multiplexer-capture" \
|
||||
"aliases/monitor" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"overrides/environment" \
|
||||
"integrations/pkg-upgrade" \
|
||||
"autoexec/telemetry" \
|
||||
"integrations/notifications" \
|
||||
"autoexec/pkg-wrappers" \
|
||||
"logging/pkg-logs" \
|
||||
"aliases/network" \
|
||||
"aliases/filesystem"
|
||||
|
||||
autoexec/venv \
|
||||
logging/terminal-capture \
|
||||
autoexec/sync \
|
||||
logging/multiplexer-capture \
|
||||
integrations/terminal-abbrs \
|
||||
overrides/key-bindings \
|
||||
aliases/dev-tools \
|
||||
autoexec/sync \
|
||||
overrides/key-bindings \
|
||||
aliases/shell-tools \
|
||||
overrides/key-bindings \
|
||||
aliases/filesystem \
|
||||
aliases/dev-tools \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
overrides/environment \
|
||||
overrides/environment \
|
||||
overrides/key-bindings \
|
||||
greeting/greeting-message \
|
||||
overrides/environment \
|
||||
overrides/environment \
|
||||
overrides/privacy \
|
||||
overrides/key-bindings \
|
||||
integrations/notifications \
|
||||
aliases/filesystem \
|
||||
aliases/dev-tools \
|
||||
autoexec/plugin-management \
|
||||
greeting/first-run \
|
||||
overrides/prompt \
|
||||
aliases/shell-tools \
|
||||
integrations/history-logs \
|
||||
overrides/key-bindings \
|
||||
logging/terminal-capture \
|
||||
logging/terminal-capture \
|
||||
aliases/shell-tools \
|
||||
integrations/history-logs \
|
||||
aliases/filesystem \
|
||||
aliases/filesystem \
|
||||
aliases/filesystem \
|
||||
autoexec/pkg-wrappers \
|
||||
logging/pkg-logs \
|
||||
aliases/network \
|
||||
overrides/key-bindings \
|
||||
aliases/search \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
logging/terminal-capture \
|
||||
integrations/window-mgmt \
|
||||
integrations/window-mgmt \
|
||||
aliases/network \
|
||||
overrides/prompt \
|
||||
integrations/window-mgmt \
|
||||
overrides/prompt \
|
||||
logging/multiplexer-capture \
|
||||
aliases/monitor \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
overrides/environment \
|
||||
integrations/pkg-upgrade \
|
||||
autoexec/telemetry \
|
||||
integrations/notifications \
|
||||
autoexec/pkg-wrappers \
|
||||
logging/pkg-logs \
|
||||
aliases/network \
|
||||
aliases/filesystem
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
# site abbr-integrations: integrations/terminal-abbrs
|
||||
# site abbr-overrides: overrides/key-bindings
|
||||
|
||||
# Abbreviations only expand in the line editor; a script can never use one.
|
||||
status is-interactive; or return
|
||||
|
||||
# Neovim
|
||||
# @category Editors
|
||||
# @desc nvim
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
#
|
||||
# Manage the registry with: auto-pull add / remove / list / status
|
||||
|
||||
# Registers an --on-variable PWD handler that backgrounds a git fetch. In a
|
||||
# script that cd's, that is both wasted work and AGENTS.md Task #4's
|
||||
# credential-prompt hazard fired from a background job.
|
||||
status is-interactive; or return
|
||||
|
||||
# C2 guard: when auto-execution is disabled, do not register the handler.
|
||||
__fish_config_op_enabled (status basename); or exit
|
||||
|
||||
|
||||
+23
-15
@@ -7,14 +7,20 @@
|
||||
# Provides bash-style history expansion functions for abbreviations.
|
||||
# These functions are gated by the C3 overrides switch.
|
||||
|
||||
# The six expand_* functions are reachable only through abbr --function
|
||||
# (conf.d/abbr.fish:677-697), i.e. only during interactive expansion.
|
||||
status is-interactive; or return
|
||||
|
||||
# Execute expand_bang_all
|
||||
function expand_bang_all --description 'Execute expand_bang_all'
|
||||
# Opinionated guard (C3): no expansion when overrides are disabled.
|
||||
__fish_config_op_enabled (status basename); or return 1
|
||||
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
set -l tokens (string split -n " " -- $history[1])
|
||||
if test (count $tokens) -gt 1
|
||||
echo -- (string join " " -- $tokens[2..-1])
|
||||
@@ -42,12 +48,14 @@ function expand_bang_minus_n --description 'Execute expand_bang_minus_n'
|
||||
__fish_config_op_enabled (status basename); or return 1
|
||||
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
# Extract the number from the regex match
|
||||
if string match -qr '!-(\d+)' -- "$token"
|
||||
set -l n (string match -r '!-(\d+)' -- "$token")[2]
|
||||
|
||||
|
||||
if test (count $history) -ge $n
|
||||
echo -- $history[$n]
|
||||
else
|
||||
@@ -67,20 +75,20 @@ function expand_bang_search --description 'Execute expand_bang_search'
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
|
||||
# Extract query: looks for text after !? and before an optional ?
|
||||
set -l query (string match -r '!\?([^?]+)' -- $token)[2]
|
||||
|
||||
|
||||
if test -n "$query"
|
||||
# Search history for a match anywhere in the command
|
||||
set -l match (builtin history search --contains --max=1 -- $query)
|
||||
|
||||
|
||||
if test -n "$match"
|
||||
echo -- $match
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
echo -- $token
|
||||
end
|
||||
|
||||
@@ -94,20 +102,20 @@ function expand_bang_string --description 'Execute expand_bang_string'
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
|
||||
# Remove the '!' to get the search query
|
||||
set -l query (string sub -s 2 -- $token)
|
||||
|
||||
|
||||
if test -n "$query"
|
||||
# Search history for a prefix match
|
||||
set -l match (builtin history search --prefix --max=1 -- $query)
|
||||
|
||||
|
||||
if test -n "$match"
|
||||
echo -- $match
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# If no match or empty query, return the token so it doesn't vanish
|
||||
echo -- $token
|
||||
end
|
||||
@@ -124,12 +132,12 @@ function expand_typo_sub --description 'Execute expand_typo_sub'
|
||||
if test -z "$current_token"
|
||||
set current_token (commandline -t)
|
||||
end
|
||||
|
||||
|
||||
if string match -qr '\^([^^]+)\^([^^]*)' -- "$current_token"
|
||||
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$current_token")
|
||||
set -l old $captured[2]
|
||||
set -l new $captured[3]
|
||||
|
||||
|
||||
if test -n "$old"
|
||||
# Using -- to ensure strings starting with '-' aren't treated as flags
|
||||
echo -- (string replace -a -- "$old" "$new" "$last_cmd")
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
# This allows for rapid-fire math without leaving the current shell.
|
||||
# ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Defines only fish_user_key_bindings, which fish calls from the interactive
|
||||
# reader and nowhere else.
|
||||
status is-interactive; or return
|
||||
|
||||
function fish_user_key_bindings
|
||||
|
||||
# Custom key chords are opinionated (C3 overrides); skip them entirely
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
# solely in functions/ are never registered and their --on-variable triggers
|
||||
# never fire.
|
||||
|
||||
# Calls __fish_config_sync_logging at every shell start, which mkdir+touches
|
||||
# the C5 sentinel on disk. Its only consumers — the Kitty watcher and the
|
||||
# paru/yay wrappers — are interactive-context; every interactive shell still
|
||||
# refreshes it.
|
||||
status is-interactive; or return
|
||||
|
||||
function __fish_config_logging_changed --on-variable __fish_config_op_logging \
|
||||
--description 'C5 event handler: sync logging state when __fish_config_op_logging changes'
|
||||
__fish_config_sync_logging
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
# site paru-autoexec: autoexec/pkg-wrappers
|
||||
# site paru-logging: logging/pkg-logs
|
||||
|
||||
# Defines nothing; its only effect is generating ~/.local/bin/paru, an
|
||||
# idempotent write every interactive session already performs.
|
||||
status is-interactive; or return
|
||||
|
||||
# Auto-generating a wrapper in ~/.local/bin is opinionated (C2 auto-exec).
|
||||
# Wrapper generation is also gated by C5 (Logging & Capture).
|
||||
__fish_config_op_enabled (status basename) paru-autoexec; or return
|
||||
@@ -56,7 +60,7 @@ printf '%s\n' \
|
||||
"cmd_str=\"$_paru_real\"" \
|
||||
'for arg in "$@"; do' \
|
||||
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
|
||||
'done' \
|
||||
done \
|
||||
'script -q -e -c "$cmd_str" "$log_file"' \
|
||||
'exit_code=$?' \
|
||||
'' \
|
||||
@@ -66,19 +70,18 @@ printf '%s\n' \
|
||||
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
|
||||
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
|
||||
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
|
||||
'else' \
|
||||
else \
|
||||
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
|
||||
'fi' \
|
||||
fi \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
done \
|
||||
'' \
|
||||
'exit $exit_code' \
|
||||
> $_paru_wrapper
|
||||
'exit $exit_code' >$_paru_wrapper
|
||||
|
||||
chmod +x $_paru_wrapper
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
# Without starship, fish's built-in prompt already emits OSC 133;A
|
||||
# on the prompt line itself, so no wrapper is needed.
|
||||
|
||||
# Defines fish_prompt; no script renders a prompt. Checked before the
|
||||
# op-guard so the builtin short-circuits ahead of three function autoloads.
|
||||
status is-interactive; or return
|
||||
|
||||
# Replacing the prompt is opinionated (C3 overrides)
|
||||
__fish_config_op_enabled (status basename); or return
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ if not __fish_config_op_enabled (status basename)
|
||||
return
|
||||
end
|
||||
|
||||
# Below the cleanup block on purpose: that branch erases a stale universal
|
||||
# FZF_DEFAULT_OPTS and must keep running wherever it runs today. Everything
|
||||
# past here is fish_color_* for the syntax highlighter, interactive-only.
|
||||
status is-interactive; or return
|
||||
|
||||
# ────────────────────── Syntax highlighting colors ──────────────────────
|
||||
set --global fish_color_autosuggestion 6c7086
|
||||
set --global fish_color_cancel f38ba8
|
||||
|
||||
+24
-12
@@ -18,20 +18,32 @@ if test -f ~/.fish_profile
|
||||
source ~/.fish_profile
|
||||
end
|
||||
|
||||
# Append unique directories to $PATH (fish_add_path handles duplicates automatically)
|
||||
fish_add_path ~/.local/bin
|
||||
fish_add_path ~/Applications/depot_tools
|
||||
# This file is sourced twice per shell on CachyOS (once by the conf.d
|
||||
# autoload, once forced by config.fish to re-win over the distro's own
|
||||
# tricks.fish). The PATH/MANPAGER setup below doesn't need to repeat on
|
||||
# the second pass -- only the functions/aliases further down do, since
|
||||
# those are what re-assert over the distro config. Gate the expensive
|
||||
# calls (fish_add_path, type -q bat) behind a once-per-session guard.
|
||||
# ponytail: per-session guard, not per-value; if this file grows more
|
||||
# expensive one-time setup, extend the same guard rather than adding more.
|
||||
if not set -q __fish_config_tricks_env_applied
|
||||
set -g __fish_config_tricks_env_applied 1
|
||||
|
||||
# Expose user-local man pages
|
||||
if not contains ~/.local/share/man $MANPATH
|
||||
set -gx MANPATH ~/.local/share/man $MANPATH
|
||||
end
|
||||
# Append unique directories to $PATH (fish_add_path handles duplicates automatically)
|
||||
fish_add_path ~/.local/bin
|
||||
fish_add_path ~/Applications/depot_tools
|
||||
|
||||
# Format man pages using bat (only if bat is installed)
|
||||
# Overriding $MANPAGER is opinionated (C3 overrides)
|
||||
if type -q bat; and __fish_config_op_enabled (status basename) tricks-manpager
|
||||
set -gx MANROFFOPT -c
|
||||
set -gx MANPAGER "sh -c 'col -bx | bat -l man -p'"
|
||||
# Expose user-local man pages
|
||||
if not contains ~/.local/share/man $MANPATH
|
||||
set -gx MANPATH ~/.local/share/man $MANPATH
|
||||
end
|
||||
|
||||
# Format man pages using bat (only if bat is installed)
|
||||
# Overriding $MANPAGER is opinionated (C3 overrides)
|
||||
if type -q bat; and __fish_config_op_enabled (status basename) tricks-manpager
|
||||
set -gx MANROFFOPT -c
|
||||
set -gx MANPAGER "sh -c 'col -bx | bat -l man -p'"
|
||||
end
|
||||
end
|
||||
|
||||
# Set settings for https://github.com/franciscolourenco/done
|
||||
|
||||
+31
-26
@@ -9,6 +9,10 @@
|
||||
# site wakatime-autoexec: autoexec/telemetry
|
||||
# site wakatime-hook: integrations/notifications
|
||||
|
||||
# Registers a fish_postexec handler; that event is emitted only by the
|
||||
# interactive reader, so the handler is dead weight in a script.
|
||||
status is-interactive; or return
|
||||
|
||||
# Local modification: opinionated guard (AGENTS.md Task #3). WakaTime
|
||||
# reporting is classified under both C2 auto-execution and C4 integrations;
|
||||
# disabling either category skips registering the hook.
|
||||
@@ -16,37 +20,38 @@ __fish_config_op_enabled (status basename) wakatime-autoexec; or exit
|
||||
__fish_config_op_enabled (status basename) wakatime-hook; or exit
|
||||
|
||||
function __register_wakatime_fish_before_exec -e fish_postexec
|
||||
if set -q FISH_WAKATIME_DISABLED
|
||||
return 0
|
||||
end
|
||||
|
||||
set -l exec_command_str
|
||||
if set -q FISH_WAKATIME_DISABLED
|
||||
return 0
|
||||
end
|
||||
|
||||
set exec_command_str (string split -f1 ' ' "$argv")
|
||||
set -l exec_command_str
|
||||
|
||||
if test "$exec_command_str" = 'exit'
|
||||
return 0
|
||||
end
|
||||
set exec_command_str (string split -f1 ' ' "$argv")
|
||||
|
||||
set -l PLUGIN_NAME "ik11235/wakatime.fish"
|
||||
set -l PLUGIN_VERSION "0.0.6"
|
||||
if test "$exec_command_str" = exit
|
||||
return 0
|
||||
end
|
||||
|
||||
set -l project
|
||||
set -l wakatime_path
|
||||
set -l PLUGIN_NAME "ik11235/wakatime.fish"
|
||||
set -l PLUGIN_VERSION "0.0.6"
|
||||
|
||||
if type -p wakatime 2>&1 > /dev/null
|
||||
set wakatime_path (type -p wakatime)
|
||||
else if type -p ~/.wakatime/wakatime-cli 2>&1 > /dev/null
|
||||
set wakatime_path (type -p ~/.wakatime/wakatime-cli)
|
||||
else
|
||||
return 1
|
||||
end
|
||||
set -l project
|
||||
set -l wakatime_path
|
||||
|
||||
if git rev-parse --is-inside-work-tree &> /dev/null
|
||||
set project (basename (git rev-parse --show-toplevel))
|
||||
else
|
||||
set project "Terminal"
|
||||
end
|
||||
if type -p wakatime 2>&1 >/dev/null
|
||||
set wakatime_path (type -p wakatime)
|
||||
else if type -p ~/.wakatime/wakatime-cli 2>&1 >/dev/null
|
||||
set wakatime_path (type -p ~/.wakatime/wakatime-cli)
|
||||
else
|
||||
return 1
|
||||
end
|
||||
|
||||
$wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &> /dev/null&; disown
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null
|
||||
set project (basename (git rev-parse --show-toplevel))
|
||||
else
|
||||
set project Terminal
|
||||
end
|
||||
|
||||
$wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &>/dev/null &
|
||||
disown
|
||||
end
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
# site yay-autoexec: autoexec/pkg-wrappers
|
||||
# site yay-logging: logging/pkg-logs
|
||||
|
||||
# Defines nothing; its only effect is generating ~/.local/bin/yay, an
|
||||
# idempotent write every interactive session already performs.
|
||||
status is-interactive; or return
|
||||
|
||||
# Auto-generating a wrapper in ~/.local/bin is opinionated (C2 auto-exec).
|
||||
# Wrapper generation is also gated by C5 (Logging & Capture).
|
||||
__fish_config_op_enabled (status basename) yay-autoexec; or return
|
||||
@@ -56,7 +60,7 @@ printf '%s\n' \
|
||||
"cmd_str=\"$_yay_real\"" \
|
||||
'for arg in "$@"; do' \
|
||||
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
|
||||
'done' \
|
||||
done \
|
||||
'script -q -e -c "$cmd_str" "$log_file"' \
|
||||
'exit_code=$?' \
|
||||
'' \
|
||||
@@ -66,19 +70,18 @@ printf '%s\n' \
|
||||
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
|
||||
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
|
||||
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
|
||||
'else' \
|
||||
else \
|
||||
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
|
||||
'fi' \
|
||||
fi \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
done \
|
||||
'' \
|
||||
'exit $exit_code' \
|
||||
> $_yay_wrapper
|
||||
'exit $exit_code' >$_yay_wrapper
|
||||
|
||||
chmod +x $_yay_wrapper
|
||||
|
||||
|
||||
+10
-1
@@ -25,6 +25,7 @@
|
||||
# site cachyos-tricks: overrides/environment
|
||||
# site cachyos-strip-aliases: aliases/filesystem
|
||||
# site cachyos-strip-overrides: overrides/key-bindings
|
||||
# site privacy: overrides/privacy
|
||||
# site pager-editor-gpg: overrides/environment
|
||||
# site exit-wiring: overrides/key-bindings
|
||||
# site path-setup: overrides/environment
|
||||
@@ -104,6 +105,14 @@ set -q NVIDIA_SETTINGS_RW_CONFIG_FILE; or set -gx NVIDIA_SETTINGS_RW_CONFIG_FILE
|
||||
set -q CODEIUM_HOME; or set -gx CODEIUM_HOME "$XDG_CONFIG_HOME/codeium"
|
||||
set -q WORDLIST; or set -gx WORDLIST "$XDG_CONFIG_HOME/hunspell_en_US"
|
||||
|
||||
# ───────────────────────── Privacy variables ────────────────────────────
|
||||
# Global telemetry opt-out variables (C3 overrides: privacy)
|
||||
# Various CLI tools, runtimes, and AI-agent tools respect these variables.
|
||||
if __fish_config_op_enabled (status basename) privacy
|
||||
set -gx DO_NOT_TRACK 1
|
||||
set -gx DISABLE_TELEMETRY 1
|
||||
end
|
||||
|
||||
# ─────────────────────────── Pager variables ────────────────────────────
|
||||
# Overriding $PAGER, $EDITOR, and $GPG_TTY is opinionated (C3 overrides)
|
||||
if __fish_config_op_enabled (status basename) pager-editor-gpg
|
||||
@@ -244,7 +253,7 @@ if status is-interactive
|
||||
#
|
||||
# Resolve user-dots path. Customize via: set -U __fish_user_dots_path /your/path
|
||||
set -q __fish_user_dots_path
|
||||
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
|
||||
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
|
||||
# ────────────────────── user-dots convenience symlink ───────────────────
|
||||
# Keep $__fish_config_dir/user-dots tracking the resolved path so it can be
|
||||
# browsed from the fish config dir. Git-ignored. Controlled by the
|
||||
|
||||
+188
-17
@@ -8,12 +8,14 @@
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import functools
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import codespans
|
||||
import manualtools as mt
|
||||
import generate_component_registry
|
||||
|
||||
@@ -21,6 +23,8 @@ DOCS = Path(__file__).parent
|
||||
MANUAL = DOCS / "manual"
|
||||
FUNCTIONS = DOCS.parent / "functions"
|
||||
COMPLETIONS = DOCS.parent / "completions"
|
||||
README = DOCS.parent / "README.md"
|
||||
REPO_BLOB_BASE = "https://git.rootiest.dev/rootiest/fish-config/src/branch/main/"
|
||||
SLUG_DIR = "reference"
|
||||
|
||||
# File-tree branches whose real directory contents get listed inline on the
|
||||
@@ -78,10 +82,104 @@ def _with_abbreviations(body: str, abbrs: dict[str, list[dict]]) -> str:
|
||||
for cat, table in rendered_abbrs.items():
|
||||
placeholder = f"<!-- GENERATED: {cat} -->"
|
||||
body = body.replace(placeholder, table)
|
||||
|
||||
|
||||
return body
|
||||
|
||||
|
||||
TOC_PLACEHOLDER = "<!-- GENERATED: toc -->"
|
||||
TOC_SKIP_STEMS = {"index"}
|
||||
|
||||
|
||||
def _build_toc(root: Path) -> str:
|
||||
"""Render the section list for docs/manual/00-table-of-contents.md.
|
||||
|
||||
Walks the same tree `build_concat` does, so it can never drift from the
|
||||
man page's actual section order. `index.md` and the `00-*` front-matter
|
||||
pages (Name, Synopsis, this page) sit before section 1 and are excluded,
|
||||
same as any `man: false` page (currently only 404).
|
||||
"""
|
||||
lines: list[str] = []
|
||||
n = 0
|
||||
for path, depth in mt.walk(root):
|
||||
rel = path.relative_to(root)
|
||||
# len(rel.parts) == 1 means a root-level file, not a directory's own
|
||||
# index page (e.g. 04-abbreviations/index.md), which must keep its
|
||||
# own numbered line even though its stem is also "index".
|
||||
if depth == 0 and len(rel.parts) == 1 and (rel.stem in TOC_SKIP_STEMS or rel.stem.startswith("00-")):
|
||||
continue
|
||||
fm, _ = mt.parse(path)
|
||||
if not fm.get("man", True):
|
||||
continue
|
||||
title = fm.get("title", path.stem)
|
||||
if depth == 0:
|
||||
n += 1
|
||||
lines.append(f" {n}. {title}")
|
||||
else:
|
||||
lines.append(f" - {title}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _with_toc(body: str, root: Path) -> str:
|
||||
"""Inject the `<!-- GENERATED: toc -->` placeholder with the built section list."""
|
||||
return body.replace(TOC_PLACEHOLDER, _build_toc(root)) if TOC_PLACEHOLDER in body else body
|
||||
|
||||
|
||||
README_LINK_RE = re.compile(r"\]\((?!https?://|#|mailto:)([^)]+)\)")
|
||||
README_FENCE_RE = re.compile(r"```[^\n]*\n(.*?)```\n?", re.DOTALL)
|
||||
README_PLACEHOLDER_RE = re.compile(r"<!-- README: (.+?) -->")
|
||||
|
||||
|
||||
def _rewrite_repo_links(text: str) -> str:
|
||||
"""Point a README-relative link (`CONTRIBUTING.md`, `LICENSE`) at its file on Gitea."""
|
||||
return README_LINK_RE.sub(lambda m: f"]({REPO_BLOB_BASE}{m.group(1)})", text)
|
||||
|
||||
|
||||
def _defence(text: str) -> str:
|
||||
"""Rewind a README fenced code block into the manual's indented-block form.
|
||||
|
||||
`docs/manual` bodies are authored man-page style (4-space indent), never
|
||||
fenced: `codespans`/pandoc pair backticks per line, and a fence line's
|
||||
triple backtick throws that count off. README.md is ordinary markdown
|
||||
and fences its examples, so an injected section is converted back.
|
||||
"""
|
||||
def repl(m: re.Match) -> str:
|
||||
block = m.group(1).rstrip("\n")
|
||||
return "\n".join(" " + line for line in block.split("\n")) + "\n"
|
||||
|
||||
return README_FENCE_RE.sub(repl, text)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _readme_sections() -> dict[str, str]:
|
||||
"""Split README.md into {H2 heading: body}, links rewritten to point at the repo.
|
||||
|
||||
Lets a manual stub pull one README section in verbatim via a
|
||||
`<!-- README: <Heading> -->` placeholder, so the README stays the single
|
||||
source of truth for sections that describe the repo itself rather than
|
||||
the shell config (Testing, Contributing, Attribution, License).
|
||||
"""
|
||||
sections: dict[str, str] = {}
|
||||
heading: str | None = None
|
||||
lines: list[str] = []
|
||||
for line in README.read_text().split("\n") + ["## "]:
|
||||
if line.startswith("## "):
|
||||
if heading is not None:
|
||||
body = "\n".join(lines).strip()
|
||||
if body.endswith("---"):
|
||||
body = body[:-3].rstrip()
|
||||
sections[heading] = _defence(_rewrite_repo_links(body))
|
||||
heading = line[3:].strip()
|
||||
lines = []
|
||||
else:
|
||||
lines.append(line)
|
||||
return sections
|
||||
|
||||
|
||||
def _with_readme(body: str) -> str:
|
||||
"""Inject `<!-- README: Heading -->` placeholders with that README section's body."""
|
||||
return README_PLACEHOLDER_RE.sub(lambda m: _readme_sections().get(m.group(1), ""), body)
|
||||
|
||||
|
||||
def build_concat(root: Path) -> str:
|
||||
"""Concatenate the manual into one ordered markdown document.
|
||||
|
||||
@@ -93,6 +191,16 @@ def build_concat(root: Path) -> str:
|
||||
with no frontmatter fences and no Astro-visible frontmatter key. When
|
||||
present, its contents are re-emitted byte-for-byte as the leading
|
||||
`---`-fenced block, ahead of every heading.
|
||||
|
||||
Bodies go through `codespans` here so a token is typeset the same way
|
||||
in every output: `tmux` and `local.fish` are wrapped on the site by
|
||||
that pass, and without it the man page marked only what the SSOT
|
||||
happened to backtick by hand. Section 5 is unaffected -- its entries
|
||||
arrive as indented verbatim blocks, which `codespans` leaves alone and
|
||||
pandoc already sets in a monospace font.
|
||||
|
||||
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))
|
||||
chunks: list[str] = []
|
||||
@@ -111,10 +219,13 @@ def build_concat(root: Path) -> str:
|
||||
elif "04-abbreviations" in path.parts:
|
||||
abbrs = mt.parse_abbreviations(DOCS.parent / "conf.d")
|
||||
body = _with_abbreviations(body, abbrs)
|
||||
body = _with_readme(body)
|
||||
body = _with_toc(body, root)
|
||||
if body:
|
||||
body = re.sub(r"<LinkButton.*?</LinkButton>\n*", "", body, flags=re.DOTALL)
|
||||
body = re.sub(r"<CardGrid.*?</CardGrid>\n*", "", body, flags=re.DOTALL)
|
||||
body = re.sub(r"\[([^\]]+)\]\(/[^)]+\)", r"\1", body)
|
||||
body = codespans.add_code_spans(body, _code_vocabulary())
|
||||
chunks.append(mt.shift_headings(body, depth))
|
||||
return "\n\n".join(chunks) + "\n"
|
||||
|
||||
@@ -195,7 +306,7 @@ SHELL_HEADS = frozenset(
|
||||
if jobs kitty ls man math mkdir mv nvim npm pacman paru pip pip3 pkg printf
|
||||
python python3 rm set shutdown source string sudo switch systemctl test time
|
||||
tmux touch trash type wget wezterm while yay zellij zypper
|
||||
fish_default_key_bindings fish_vi_key_bindings
|
||||
fish_default_key_bindings fish_vi_key_bindings fish_config
|
||||
""".split()
|
||||
)
|
||||
|
||||
@@ -223,7 +334,16 @@ def _is_prose(para: list[str]) -> bool:
|
||||
|
||||
|
||||
def _is_shell(para: list[str], entry_name: str | None) -> bool:
|
||||
"""True when every line of a paragraph looks like a shell command."""
|
||||
"""True when every line of a paragraph looks like a shell command.
|
||||
|
||||
Recognised command names are `SHELL_HEADS` plus the same code
|
||||
vocabulary `codespans` wraps in backticks (repo function names and
|
||||
`fish-deps` catalog entries included) — one shared list instead of a
|
||||
second hand-maintained one that silently drifts, which is how
|
||||
`fish-deps`/`config-settings`-style custom commands used to fall
|
||||
through to an unhighlighted block.
|
||||
"""
|
||||
vocab = SHELL_HEADS | _code_vocabulary().full
|
||||
name_re = (
|
||||
re.compile(rf"(?<![\w-]){re.escape(entry_name)}(?![\w-])")
|
||||
if entry_name
|
||||
@@ -235,7 +355,7 @@ def _is_shell(para: list[str], entry_name: str | None) -> bool:
|
||||
continue
|
||||
if name_re and name_re.search(stripped):
|
||||
continue
|
||||
if stripped.split()[0].lstrip("$").rstrip(";") not in SHELL_HEADS:
|
||||
if stripped.split()[0].lstrip("$").rstrip(";") not in vocab:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -250,6 +370,30 @@ PATH_LINE_RE = re.compile(r"^[~$][\w./{}-]*\.\w+$")
|
||||
# as a literal comment inside the code.
|
||||
FILENAME_COMMENT_RE = re.compile(r"^#\s*(?:in\s+)?([$~\w./-]+\.\w+)\s*$")
|
||||
|
||||
# A leading comment that isn't a filename can still name what the block is
|
||||
# about (e.g. "# Arch / AUR" heading a distro's install command) rather
|
||||
# than explain a step ("# Turn it off:") — the trailing-punctuation and
|
||||
# length checks in `_label_title` are what tell the two apart.
|
||||
LABEL_COMMENT_RE = re.compile(r"^#\s*(.+)$")
|
||||
|
||||
|
||||
def _label_title(line: str) -> str | None:
|
||||
"""A short, label-shaped leading comment, promoted to a fence title.
|
||||
|
||||
Anything that reads as a sentence — trailing `.`/`!`/`?`/`;`/`:`/`,`,
|
||||
or just long — is left as a literal comment instead: it's explaining a
|
||||
step, not naming the block.
|
||||
"""
|
||||
m = LABEL_COMMENT_RE.match(line)
|
||||
if not m:
|
||||
return None
|
||||
text = m.group(1).strip()
|
||||
if not text or text[-1] in ".!?;:,":
|
||||
return None
|
||||
if len(text) > 48 or len(text.split()) > 8:
|
||||
return None
|
||||
return text
|
||||
|
||||
CELL_SPLIT = re.compile(r"\s{2,}")
|
||||
|
||||
# A rule line under a header row — the "Component Reference" tables'
|
||||
@@ -394,18 +538,32 @@ def _render_para(para: list[str], entry_name: str | None, deeper: bool) -> str:
|
||||
path = para[0].strip()
|
||||
name = path.rsplit("/", 1)[-1]
|
||||
return f'```fish title="{name}"\n{path}\n```'
|
||||
if _is_shell(para, entry_name):
|
||||
body = para
|
||||
title = None
|
||||
m = FILENAME_COMMENT_RE.match(para[0].strip())
|
||||
if m:
|
||||
title, body = m.group(1), para[1:]
|
||||
info = f'fish title="{title}"' if title else "fish"
|
||||
return f"```{info}\n" + "\n".join(body) + "\n```"
|
||||
# Checked even when `deeper`: a shell paragraph's own nested indentation
|
||||
# (a for/if/while body) must not be mistaken for a table's alignment —
|
||||
# `_is_shell` only looks at each line's first word, so it stays safe to
|
||||
# try before falling through to the table/text fallbacks below.
|
||||
if _is_shell(para, entry_name):
|
||||
body = para
|
||||
title = None
|
||||
m = FILENAME_COMMENT_RE.match(para[0].strip())
|
||||
if m:
|
||||
title, body = m.group(1), para[1:]
|
||||
else:
|
||||
label = _label_title(para[0].strip())
|
||||
if label:
|
||||
title, body = label, para[1:]
|
||||
info = f'fish title="{title}"' if title else "fish"
|
||||
return f"```{info}\n" + "\n".join(body) + "\n```"
|
||||
table = _as_ruled_table(para) or _as_table(para) or _as_file_tree(para)
|
||||
if table is not None:
|
||||
return table
|
||||
return "\n".join(INDENT + line for line in para)
|
||||
# MDX (used for any page that also carries an <Aside> or <FileTree>)
|
||||
# has no indented-code-block syntax — a plain 4-space-indented block
|
||||
# silently renders as flowed paragraph text there, collapsing every
|
||||
# line break. A fenced block works in both MDX and plain Markdown, so
|
||||
# it's the only fallback that's safe regardless of which one a given
|
||||
# page ends up promoted to.
|
||||
return "```text\n" + "\n".join(para) + "\n```"
|
||||
|
||||
|
||||
def _prettify_block(block: list[str], entry_name: str | None) -> str:
|
||||
@@ -478,12 +636,24 @@ def _as_aside(para: list[str]) -> str | None:
|
||||
return f"<Aside {attrs}>\n{body}\n</Aside>"
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def _code_vocabulary() -> codespans.Vocabulary:
|
||||
"""The command names codespans may wrap, read from the repo once."""
|
||||
return codespans.vocabulary(DOCS.parent)
|
||||
|
||||
|
||||
def prettify(body: str, entry_name: str | None = None) -> str:
|
||||
"""Rewrite a body's indented code blocks and labeled asides for the website.
|
||||
|
||||
Site-only: the man page and `config-help` keep reading the untouched
|
||||
SSOT, where the indented form and the `LABEL:` text are exactly what
|
||||
pandoc/`config-help` want.
|
||||
The block and aside rewrites are site-only: the man page and
|
||||
`config-help` keep reading the untouched SSOT, where the indented form
|
||||
and the `LABEL:` text are exactly what pandoc/`config-help` want.
|
||||
|
||||
The inline code spans added last are not site-only. `-a/--all` and
|
||||
`__fish_config_op_aliases` are authored bare so the `functions/*.fish`
|
||||
headers stay readable as plain text, and the backticks every output
|
||||
wants are put on here rather than in the SSOT -- `build_concat()` runs
|
||||
the same pass for the man page and `config-help`.
|
||||
"""
|
||||
out: list[str] = []
|
||||
block: list[str] = []
|
||||
@@ -522,7 +692,7 @@ def prettify(body: str, entry_name: str | None = None) -> str:
|
||||
while block and not block[-1].strip():
|
||||
block.pop()
|
||||
out.append(_prettify_block(block, entry_name))
|
||||
return "\n".join(out)
|
||||
return codespans.add_code_spans("\n".join(out), _code_vocabulary())
|
||||
|
||||
|
||||
ENTRY_HEADS = {
|
||||
@@ -718,6 +888,7 @@ def build_site(root: Path, out: Path) -> list[dict]:
|
||||
if "04-abbreviations" in path.parts:
|
||||
abbrs = mt.parse_abbreviations(DOCS.parent / "conf.d")
|
||||
body = _with_abbreviations(body, abbrs)
|
||||
body = _with_readme(body)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
body = _inject_subheading_cards(body)
|
||||
_write_prettified(target, _page_fm(fm), prettify(body))
|
||||
|
||||
@@ -0,0 +1,546 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
"""Wrap code-shaped tokens in inline code spans for the Starlight site.
|
||||
|
||||
Section 5 is generated from the `functions/*.fish` comment headers, which
|
||||
are read as plain text by `config-help`, by `funcsave`, and by anyone
|
||||
opening the source file. Backticks there would be noise, so the headers
|
||||
are authored without them -- and the site inherited that, rendering
|
||||
`-a/--all` and `__fish_config_op_aliases` as ordinary prose.
|
||||
|
||||
This module closes that gap at render time: it walks the markdown a page
|
||||
is about to be written as, finds the tokens whose shape only ever means
|
||||
"code" (flags, `$vars`, snake_case identifiers, paths, key chords, known
|
||||
command names) and wraps each one in a code span. The SSOT is never
|
||||
touched, so the man page and `config-help` keep the plain-text form.
|
||||
|
||||
Everything here is conservative by construction: leaving a token alone is
|
||||
always safe and wrapping the wrong one is not, so every rule bails out the
|
||||
moment it is unsure. The regions that must never be rewritten -- fenced
|
||||
blocks, indented code blocks, existing code spans, link targets, URLs, JSX
|
||||
attributes, `<FileTree>` bodies, headings -- are recognised first and
|
||||
passed through.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vocabulary
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Commands a reader expects to see typeset as code. This is the *wide* list:
|
||||
# it decides that a table column holds command lines (see _command_columns),
|
||||
# where position already proves the name is a command. Wrapping a bare
|
||||
# mention in running prose is gated on the strict tier below.
|
||||
STANDARD_COMMANDS = frozenset(
|
||||
"""
|
||||
apk apt awk basename bash bat bg bind brew builtin cargo cat cd chmod
|
||||
chown chsh cmp column cp curl cut date dd delta df diff dig dirname dnf
|
||||
docker dpkg du echo emacs emerge env eza exec exit export fastfetch fd
|
||||
fdisk fg fgrep file find fisher flatpak fzf gh git grep gzip head help
|
||||
hexdump host hostname id ifconfig install ip jq jobs journalctl kill
|
||||
killall kitten kitty last less ln locale ls lsblk lsd lsof make man
|
||||
micro mkdir more mount mpv mv nano nc neofetch neovim netstat nix nl
|
||||
nohup npm nproc nvim od open openssl pacman paru paste pgrep ping pip
|
||||
pip3 pkill pr printf ps pwd python python3 readlink realpath rg rm rmdir
|
||||
rpm rsync scp sed seq sh shutdown sleep snap sort source ssh stat
|
||||
strings su sudo sync systemctl tac tail tar tee test time tldr tmux
|
||||
touch tr trash tree type udisksctl umount uname uniq unzip uv vdir vi
|
||||
vim vlc wait wc wezterm wget which who whoami wl-copy wl-paste xargs
|
||||
xbps-install xclip xdg-open xsel yay yum yt-dlp zellij zip zoxide zsh
|
||||
zypper
|
||||
abbr alias and argparse begin block break case command complete contains
|
||||
continue count else emit end eval false for function funcsave functions
|
||||
history if math not or random read return set set_color status string
|
||||
switch true while
|
||||
""".split()
|
||||
)
|
||||
|
||||
# Names that also read as ordinary English (or as this manual's own prose)
|
||||
# often enough that a bare mention is not evidence of a command. They still
|
||||
# take part in command-line and list detection, where position disambiguates
|
||||
# -- they just never get wrapped on their own.
|
||||
AMBIGUOUS_COMMANDS = frozenset(
|
||||
"""
|
||||
abbr alias all and at basename bat begin bg bind block branch break case
|
||||
cat cd cheat cleanup clone column command complete contains continue copy
|
||||
count cut date dd df dir dirname do docker du duf dust echo edit else emit
|
||||
end env eval exec exit export false fc fg file find fish for free function
|
||||
functions git go head help hist history host hostname id if in install ip
|
||||
jobs join key kill last less link list ln lock locale log logs look ls make
|
||||
man math micro more mount mv next no not note od open or ov p page paste
|
||||
pkg poke ports pr ps pwd random read real replay return rm run screen sed
|
||||
search seq set sh show sleep sort source spark split stat status string
|
||||
strings su switch sync tab tac tail tar tee test time top touch tr trash
|
||||
tree true type uniq upgrade view vi wait watch wc which while who write
|
||||
yes zip
|
||||
builtin fast function vdir
|
||||
""".split()
|
||||
)
|
||||
|
||||
# Extensions that make a bare `name.ext` token unambiguously a filename.
|
||||
PATH_EXTENSIONS = (
|
||||
"fish md mdx json jsonc toml yml yaml py sh bash zsh lua conf cfg ini "
|
||||
"txt log list service socket desktop css scss ts js astro nix rasi 1"
|
||||
).split()
|
||||
|
||||
# English function words. A candidate command line containing one is prose.
|
||||
STOPWORDS = frozenset(
|
||||
"""
|
||||
a an the this that these those it its is are was were be been being am
|
||||
to of in into on at by for from with without within about across after
|
||||
before during over under again then than so such as and or but nor if
|
||||
when while where which who whom whose why how all any both each few more
|
||||
most other some only own same too very can will just should now via per
|
||||
also either neither every no not
|
||||
""".split()
|
||||
)
|
||||
|
||||
_CATALOG_ARRAY_RE = re.compile(
|
||||
r"set\s+-g\s+_fdc_(?:bins|cargo|pm)\s+((?:[^\n]*\\\n)*[^\n]*)"
|
||||
)
|
||||
|
||||
|
||||
def dependency_names(repo: Path) -> set[str]:
|
||||
"""Every tool name in the `fish-deps` catalog (`_fdc_*` arrays).
|
||||
|
||||
`functions/_fish_deps_catalog.fish` is this repo's dependency database;
|
||||
reading it here means a tool added there starts rendering as code with
|
||||
no second list to keep in sync.
|
||||
"""
|
||||
path = repo / "functions" / "_fish_deps_catalog.fish"
|
||||
if not path.exists():
|
||||
return set()
|
||||
names: set[str] = set()
|
||||
for m in _CATALOG_ARRAY_RE.finditer(path.read_text(encoding="utf-8")):
|
||||
for token in m.group(1).replace("\\\n", " ").split():
|
||||
token = token.strip("\"'")
|
||||
if token and re.fullmatch(r"[\w.@+-]+", token):
|
||||
names.add(token)
|
||||
return names
|
||||
|
||||
|
||||
def function_names(repo: Path) -> set[str]:
|
||||
"""Public function names, from the `functions/` directory listing.
|
||||
|
||||
Underscore-prefixed internals are skipped only because the snake_case
|
||||
rule already covers them, and covers them everywhere -- including the
|
||||
ones that have no file of their own.
|
||||
"""
|
||||
directory = repo / "functions"
|
||||
if not directory.is_dir():
|
||||
return set()
|
||||
return {p.stem for p in directory.glob("*.fish") if not p.stem.startswith("_")}
|
||||
|
||||
|
||||
class Vocabulary:
|
||||
"""The command names the rules recognise, in two tiers.
|
||||
|
||||
`full` is every name we know of, used where position already proves a
|
||||
token is a command (a command-line table cell, an arrow chain, a
|
||||
comma-separated run). `strict` is the subset safe to wrap on sight in
|
||||
running prose: `zoxide` yes, `find` no.
|
||||
"""
|
||||
|
||||
__slots__ = ("full", "strict")
|
||||
|
||||
def __init__(self, names: set[str]):
|
||||
# `and`, `or`, `not`, `if` … are fish builtins, but as vocabulary
|
||||
# entries they turn every conjunction into a command name and break
|
||||
# list and command-line detection. They are never worth wrapping.
|
||||
self.full = frozenset(names) - STOPWORDS
|
||||
self.strict = frozenset(
|
||||
n
|
||||
for n in names
|
||||
if n not in AMBIGUOUS_COMMANDS
|
||||
and (len(n) >= 3 or any(c.isdigit() for c in n))
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, Vocabulary)
|
||||
and self.full == other.full
|
||||
and self.strict == other.strict
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.full, self.strict))
|
||||
|
||||
|
||||
def vocabulary(repo: Path) -> Vocabulary:
|
||||
"""Build the command vocabulary from the repo plus the standard list."""
|
||||
return Vocabulary(
|
||||
set(STANDARD_COMMANDS) | dependency_names(repo) | function_names(repo)
|
||||
)
|
||||
|
||||
|
||||
EMPTY_VOCABULARY = Vocabulary(set())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Token grammar
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# A token may not start inside a word, a path, a code span, a history
|
||||
# expansion, or a hyphenated compound: `` `zoxide` ``-backed must not see
|
||||
# `-backed` as a flag, `and/or` must not see `/or` as a path, and `!-N` must
|
||||
# not see `-N` as one either.
|
||||
BEFORE = r"(?<![\w`$/\\~.=+!-])"
|
||||
# Ruling out a trailing `/` keeps a partially-recognised slash run
|
||||
# (`grep/cp/mv/wget`, where only `grep` is in the vocabulary) from being
|
||||
# wrapped one limb at a time.
|
||||
AFTER = r"(?![\w`/])"
|
||||
|
||||
_SEG = r"[\w.@+-]+"
|
||||
# The last segment of a path may not end in `.`, so a sentence-final full
|
||||
# stop stays outside the span. A segment that is nothing but dots (`..`,
|
||||
# `...`) is the exception: there the dots are the segment.
|
||||
_LAST = r"(?:[\w.@+-]*[\w@+-]|\.+)"
|
||||
_EXT = "|".join(PATH_EXTENSIONS)
|
||||
|
||||
# Key chords: `Ctrl-R`, `Ctrl+Alt+F`. Both separators appear in the manual.
|
||||
_MODIFIER = r"(?:Ctrl|Alt|Shift|Super|Meta|Cmd|Opt)"
|
||||
_KEY = (
|
||||
r"(?:F\d{1,2}|Tab|Enter|Return|Space|Esc|Escape|Backspace|Delete|Insert"
|
||||
r"|Home|End|Up|Down|Left|Right|PgUp|PgDn|[A-Za-z0-9])"
|
||||
)
|
||||
KEYBIND = rf"{_MODIFIER}(?:[+-]{_MODIFIER})*[+-]{_KEY}"
|
||||
|
||||
# `$EDITOR`, `${var}`, `$XDG_CONFIG_HOME/aichat/roles/cli.md`.
|
||||
VAR = rf"\$\{{?[A-Za-z_]\w*\}}?(?:(?:/{_SEG})*/{_LAST})?"
|
||||
|
||||
PATH = (
|
||||
rf"(?:~|\.{{1,2}})/(?:{_SEG}/)*(?:{_LAST})?" # ~/… ./… ../…
|
||||
rf"|/(?:{_SEG}/)+(?:{_LAST})?" # /etc/sudoers.d/nofail-toggle
|
||||
rf"|(?:{_SEG}/)+[\w@+-][\w.@+-]*\.(?:{_EXT})" # conf.d/abbr.fish
|
||||
rf"|[\w@+-][\w.@+-]*\.(?:{_EXT})" # config.fish
|
||||
rf"|(?:{_SEG}\.)+{_SEG}/" # conf.d/
|
||||
)
|
||||
|
||||
# `-a`, `--dry-run`, `--color=auto`. A bare `--` (this manual's ASCII em
|
||||
# dash) never matches: a letter has to follow. A single-hyphen flag is
|
||||
# capped at five characters and may not contain a hyphen, so a hyphenated
|
||||
# compound continued across a conjunction ("filesystem-inspection and
|
||||
# -modification") is not mistaken for one.
|
||||
FLAG = r"--[A-Za-z][\w-]*(?:=[\w.,:/@+-]+)?|-[A-Za-z][A-Za-z0-9]{0,4}(?:=[\w.,:/@+-]+)?"
|
||||
|
||||
# `XDG_CONFIG_HOME`, `NO_TMUX=1`. An underscore is required, so ordinary
|
||||
# acronyms (`URL`, `AGPL`, `TCP`) are never touched.
|
||||
ENVVAR = r"[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+(?:=[\w.,:/@+-]+)?"
|
||||
|
||||
# snake_case: `__fish_config_op_aliases`, `_fdc_bins`, `fish_greeting`,
|
||||
# `prompt_pwd`, `expand_bang_*`. An internal underscore is required, which
|
||||
# is also what keeps `_emphasised_` markdown out of the match.
|
||||
IDENT = r"_{0,2}[a-z][a-z0-9]*(?:_(?:[a-z0-9]+|\*))+"
|
||||
|
||||
# Regions that are already code, or are markup rather than prose. `url`
|
||||
# also covers `git@host:owner/repo.git` and `ssh://…`, whose scheme would
|
||||
# otherwise be read as a bare command name.
|
||||
PROTECTED = (
|
||||
r"(?P<code>``+.+?``+|`[^`\n]*`)"
|
||||
r"|(?P<link>\[[^\]\n]*\]\([^)\n]*\))"
|
||||
r"|(?P<url>[A-Za-z][\w+.-]*://\S+|[\w.-]+@[\w.-]+(?::\S+)?)"
|
||||
r"|(?P<tag></?[A-Za-z][^>\n]*?/?>)"
|
||||
)
|
||||
|
||||
ARROW = r"(?:->|→|=>)"
|
||||
# Shortest comma run that reads as a list of tools rather than as prose.
|
||||
MIN_RUN_NAMES = 3
|
||||
# `, and` must be tried before a bare `,` so the conjunction is a separator
|
||||
# and not an item.
|
||||
RUN_SPLIT = r",?\s+(?:and|or)\s+|,\s*"
|
||||
RUN_SPLIT_RE = re.compile(RUN_SPLIT)
|
||||
# A command name inside a chain is followed by `->`, so the usual "no
|
||||
# trailing hyphen" guard has to make room for exactly that.
|
||||
_CMD_END = r"(?!\w)(?!-(?!>))"
|
||||
|
||||
|
||||
def _alternation(names) -> str:
|
||||
"""Regex alternation over names, longest first so `rg` can't beat `rga`."""
|
||||
if not names:
|
||||
return r"(?!)"
|
||||
return "|".join(re.escape(n) for n in sorted(names, key=lambda s: (-len(s), s)))
|
||||
|
||||
|
||||
def _atom(vocab: Vocabulary) -> str:
|
||||
cmd = rf"(?:{_alternation(vocab.strict)})(?![\w-])"
|
||||
return rf"(?:{KEYBIND}|{VAR}|{PATH}|{FLAG}|{ENVVAR}|{IDENT}|{cmd})"
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=4)
|
||||
def _scanner(vocab: Vocabulary) -> re.Pattern:
|
||||
"""The single pass over a line: protected regions plus wrappable tokens."""
|
||||
full = rf"(?:{_alternation(vocab.full)})"
|
||||
chain_link = rf"(?:{full}{_CMD_END}|{VAR})"
|
||||
name = rf"{full}(?![\w-])"
|
||||
return re.compile(
|
||||
PROTECTED
|
||||
# `ls->eza, cat->bat`: a shadow chain. Position makes even an
|
||||
# ambiguous name unmistakably a command here.
|
||||
+ rf"|(?P<chain>{BEFORE}{chain_link}(?:\s*{ARROW}\s*{chain_link})+{AFTER})"
|
||||
# `cargo, starship, uv, zoxide`: a run of nothing but tool names.
|
||||
+ rf"|(?P<run>{BEFORE}{name}(?:,\s*{name})+"
|
||||
+ rf"(?:,?\s+(?:and|or)\s+{name})?{AFTER})"
|
||||
# `-a/--all`: slash-joined atoms, each wrapped on its own.
|
||||
+ rf"|(?P<group>{BEFORE}{_atom(vocab)}(?:/{_atom(vocab)})*{AFTER})"
|
||||
)
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=4)
|
||||
def _atom_re(vocab: Vocabulary) -> re.Pattern:
|
||||
return re.compile(_atom(vocab))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Table cells that are whole command lines
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# The abbreviation tables' second column is an expansion, not a sentence:
|
||||
# `sudo -s`, `cd ../..`, `journalctl -p 3 -xb`. Wrapping only the flag would
|
||||
# leave a bare `sudo` in front of a code span; the cell wants to be one span.
|
||||
#
|
||||
# Whether a column holds command lines is decided for the column as a whole
|
||||
# -- one cell is far too little evidence, as `zoxide frecency-based
|
||||
# navigation` (prose, in a column of prose) and `docker context ls` (a
|
||||
# command, in a column of commands) open identically.
|
||||
CELL_TOKEN_RE = re.compile(r"^[\w$~./=:;@+*?%'\"-]+$")
|
||||
CELL_NAME_RE = re.compile(r"^[a-z][\w.+-]*$")
|
||||
CELL_OPERATORS = frozenset((r"\|", "|", "&&", "||", ">", ">>", "<", ";"))
|
||||
MAX_CELL_TOKENS = 8
|
||||
COMMAND_COLUMN_RATIO = 0.7
|
||||
MIN_COMMAND_COLUMN_ROWS = 3
|
||||
|
||||
|
||||
def _cell_tokens(cell: str) -> list[str] | None:
|
||||
"""Tokenise a cell that could be a command line, or None if it can't be."""
|
||||
text = cell.strip()
|
||||
if not text or any(c in text for c in "`<([)]"):
|
||||
return None
|
||||
tokens = text.split()
|
||||
if not (1 <= len(tokens) <= MAX_CELL_TOKENS):
|
||||
return None
|
||||
for token in tokens:
|
||||
if token in CELL_OPERATORS:
|
||||
continue
|
||||
if not CELL_TOKEN_RE.match(token):
|
||||
return None
|
||||
if token[:1].isupper() or token.lower() in STOPWORDS:
|
||||
return None
|
||||
return tokens
|
||||
|
||||
|
||||
def _is_command_cell(cell: str, vocab: Vocabulary) -> bool:
|
||||
"""True when a cell in a command column really is one command line."""
|
||||
tokens = _cell_tokens(cell)
|
||||
if tokens is None:
|
||||
return False
|
||||
return tokens[0] in vocab.full or bool(CELL_NAME_RE.match(tokens[0]))
|
||||
|
||||
|
||||
def _opens_with_command(cell: str, vocab: Vocabulary) -> bool:
|
||||
"""The per-cell evidence the column vote is counted from."""
|
||||
tokens = _cell_tokens(cell)
|
||||
return tokens is not None and tokens[0] in vocab.full
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Line classification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
HEADING_RE = re.compile(r"^\s{0,3}#{1,6}\s")
|
||||
TABLE_ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
|
||||
TABLE_RULE_RE = re.compile(r"^\s*\|[\s:|-]+\|\s*$")
|
||||
IMPORT_RE = re.compile(r"^\s*import\s")
|
||||
FILE_TREE_OPEN = "<FileTree"
|
||||
FILE_TREE_CLOSE = "</FileTree>"
|
||||
CELL_SPLIT_RE = re.compile(r"(?<!\\)\|")
|
||||
|
||||
# A four-space indent is this manual's code block. The site never sees one
|
||||
# -- prettify() has already turned it into a fence by the time this module
|
||||
# runs -- but build_concat() keeps the indented form, because that is what
|
||||
# pandoc and `config-help` want, and its contents are code that must not be
|
||||
# rewritten: the table of contents alone would otherwise have `ov`, `bat`,
|
||||
# `less` and `cat` wrapped inside a code block.
|
||||
INDENTED_CODE = " "
|
||||
|
||||
|
||||
def _skip_line(line: str) -> bool:
|
||||
"""True for a line that must be passed through untouched.
|
||||
|
||||
Headings are excluded because Starlight derives anchors -- and this
|
||||
pipeline derives `LinkCard` hrefs -- from their text. A line opening
|
||||
with `<` is component markup, whose attributes are JSX, not markdown.
|
||||
"""
|
||||
stripped = line.strip()
|
||||
return bool(
|
||||
not stripped
|
||||
or HEADING_RE.match(line)
|
||||
or IMPORT_RE.match(line)
|
||||
or stripped.startswith("<")
|
||||
or TABLE_RULE_RE.match(line)
|
||||
)
|
||||
|
||||
|
||||
def _row_cells(line: str) -> list[str]:
|
||||
return CELL_SPLIT_RE.split(line)
|
||||
|
||||
|
||||
def _command_columns(rows: list[str], vocab: Vocabulary) -> set[int]:
|
||||
"""Which column indices of one table hold command lines rather than prose."""
|
||||
votes: dict[int, list[int]] = {}
|
||||
for line in rows:
|
||||
if TABLE_RULE_RE.match(line):
|
||||
continue
|
||||
for index, cell in enumerate(_row_cells(line)):
|
||||
if not cell.strip() or "`" in cell:
|
||||
continue
|
||||
votes.setdefault(index, []).append(_opens_with_command(cell, vocab))
|
||||
return {
|
||||
index
|
||||
for index, seen in votes.items()
|
||||
if len(seen) >= MIN_COMMAND_COLUMN_ROWS
|
||||
and sum(seen) / len(seen) >= COMMAND_COLUMN_RATIO
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The pass
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Spans this pass creates are marked, not back-ticked, until the very end:
|
||||
# adjacent ones are merged (`eza` `-l` `-a` -> `eza -l -a`), and only spans
|
||||
# this pass created may take part in that.
|
||||
MARK = "\x01"
|
||||
MERGE_RE = re.compile(rf"{MARK} {MARK}")
|
||||
|
||||
|
||||
def _mark(text: str) -> str:
|
||||
return f"{MARK}{text}{MARK}"
|
||||
|
||||
|
||||
def _wrap_atoms(text: str, atom_re: re.Pattern) -> str:
|
||||
"""Mark each atom of a slash-joined group, keeping the separators.
|
||||
|
||||
Rescanning the group rather than capturing during the first match keeps
|
||||
the grammar readable; the round-trip check makes that shortcut safe --
|
||||
if the rescan disagrees with the original match, nothing is changed.
|
||||
"""
|
||||
wrapped = atom_re.sub(lambda m: _mark(m.group(0)), text)
|
||||
if wrapped.replace(MARK, "") != text:
|
||||
return text
|
||||
return wrapped
|
||||
|
||||
|
||||
def _wrap_split(text: str, separator: str) -> str:
|
||||
"""Mark each item of a separated run, keeping the separators."""
|
||||
parts = re.split(rf"({separator})", text)
|
||||
return "".join(p if i % 2 else _mark(p) for i, p in enumerate(parts))
|
||||
|
||||
|
||||
def _transform(text: str, scanner: re.Pattern, atom_re: re.Pattern, vocab: Vocabulary) -> str:
|
||||
def repl(m: re.Match) -> str:
|
||||
group = m.lastgroup
|
||||
if group == "chain":
|
||||
return _wrap_split(m.group(0), rf"\s*{ARROW}\s*")
|
||||
if group == "run":
|
||||
# A long run anchored by at least one unambiguous tool name is
|
||||
# a list of commands; two names, one of them a word like
|
||||
# `function`, is a sentence.
|
||||
names = RUN_SPLIT_RE.split(m.group(0))
|
||||
if len(names) < MIN_RUN_NAMES or not any(
|
||||
n in vocab.strict for n in names
|
||||
):
|
||||
# Not a list after all -- hand the text back to the
|
||||
# ordinary token rules rather than swallowing it.
|
||||
return _wrap_atoms(m.group(0), atom_re)
|
||||
return _wrap_split(m.group(0), RUN_SPLIT)
|
||||
if group == "group":
|
||||
return _wrap_atoms(m.group(0), atom_re)
|
||||
return m.group(0)
|
||||
|
||||
return scanner.sub(repl, text)
|
||||
|
||||
|
||||
def _transform_line(
|
||||
line: str,
|
||||
scanner: re.Pattern,
|
||||
atom_re: re.Pattern,
|
||||
vocab: Vocabulary,
|
||||
command_columns: set[int],
|
||||
) -> str:
|
||||
if not command_columns:
|
||||
return _transform(line, scanner, atom_re, vocab)
|
||||
|
||||
out = []
|
||||
for index, cell in enumerate(_row_cells(line)):
|
||||
if index in command_columns and _is_command_cell(cell, vocab):
|
||||
body = cell.strip()
|
||||
lead = cell[: len(cell) - len(cell.lstrip())]
|
||||
trail = cell[len(cell.rstrip()) :]
|
||||
out.append(f"{lead}{_mark(body)}{trail}")
|
||||
else:
|
||||
out.append(_transform(cell, scanner, atom_re, vocab))
|
||||
return "|".join(out)
|
||||
|
||||
|
||||
def _finish(line: str) -> str:
|
||||
"""Merge abutting new spans, then turn the marks into backticks."""
|
||||
return MERGE_RE.sub(" ", line).replace(MARK, "`")
|
||||
|
||||
|
||||
def add_code_spans(text: str, vocab: Vocabulary = EMPTY_VOCABULARY) -> str:
|
||||
"""Wrap code-shaped tokens in `text` in inline code spans.
|
||||
|
||||
`text` is a rendered page body (no frontmatter). Fenced blocks,
|
||||
indented code blocks, `<FileTree>` bodies, headings, component markup,
|
||||
existing code spans, link targets and URLs are left exactly as they
|
||||
are.
|
||||
"""
|
||||
scanner = _scanner(vocab)
|
||||
atom_re = _atom_re(vocab)
|
||||
|
||||
lines = text.split("\n")
|
||||
eligible = [False] * len(lines)
|
||||
in_fence = False
|
||||
in_tree = False
|
||||
for i, line in enumerate(lines):
|
||||
if FENCE_RE.match(line):
|
||||
in_fence = not in_fence
|
||||
continue
|
||||
if in_fence:
|
||||
continue
|
||||
if FILE_TREE_OPEN in line:
|
||||
in_tree = True
|
||||
if in_tree:
|
||||
if FILE_TREE_CLOSE in line:
|
||||
in_tree = False
|
||||
continue
|
||||
if line.startswith(INDENTED_CODE):
|
||||
continue
|
||||
eligible[i] = not _skip_line(line)
|
||||
|
||||
# Command columns are a property of a whole table, so the contiguous
|
||||
# runs of table rows are resolved before any line is rewritten.
|
||||
columns: list[set[int]] = [set() for _ in lines]
|
||||
start = None
|
||||
for i, line in enumerate(lines + [""]):
|
||||
is_row = i < len(lines) and eligible[i] and TABLE_ROW_RE.match(line)
|
||||
if is_row and start is None:
|
||||
start = i
|
||||
elif not is_row and start is not None:
|
||||
found = _command_columns(lines[start:i], vocab)
|
||||
for j in range(start, i):
|
||||
columns[j] = found
|
||||
start = None
|
||||
|
||||
return "\n".join(
|
||||
_finish(_transform_line(line, scanner, atom_re, vocab, columns[i]))
|
||||
if eligible[i]
|
||||
else line
|
||||
for i, line in enumerate(lines)
|
||||
)
|
||||
+838
-271
File diff suppressed because it is too large
Load Diff
+109
-10
@@ -27,6 +27,7 @@ scrollback=## Scrollback History
|
||||
__fish_scrollback_history_dir=## Scrollback History
|
||||
__fish_scrollback_history_max_files=## Scrollback History
|
||||
scrollback-dir=## Scrollback History
|
||||
misc-vars=## Other
|
||||
|
||||
# ── Section 2: PATH ───────────────────────────────────────────
|
||||
path=# 2. PATH SETUP
|
||||
@@ -34,7 +35,6 @@ path=# 2. PATH SETUP
|
||||
# ── Section 3: Key Bindings ───────────────────────────────────
|
||||
keybindings=# 3. KEY BINDINGS
|
||||
bindings=# 3. KEY BINDINGS
|
||||
key-bindings=# 3. KEY BINDINGS
|
||||
keys=# 3. KEY BINDINGS
|
||||
fzf-bindings=## FZF Bindings (bundled from PatrickF1/fzf.fish)
|
||||
fzf=## FZF Bindings (bundled from PatrickF1/fzf.fish)
|
||||
@@ -67,6 +67,8 @@ cat=### cat
|
||||
copy=### copy
|
||||
du=### du
|
||||
dusize=### dusize
|
||||
dirs=### lD
|
||||
lsdirs=### lD
|
||||
ls=### ls
|
||||
lsr=### lsr
|
||||
lss=### lss
|
||||
@@ -76,6 +78,7 @@ ltr=### ltr
|
||||
lx=### lx
|
||||
mkdir=### mkdir
|
||||
mkcd=### mkcd
|
||||
mv=### mv
|
||||
poke=### poke
|
||||
rm=### rm
|
||||
rg=### rg
|
||||
@@ -110,6 +113,7 @@ parur=### parur
|
||||
deps=## 5.6 Dependency Management
|
||||
fish-deps=### fish-deps
|
||||
check-deps=### check_fish_deps
|
||||
fzf-update=### fzf-update
|
||||
system=## 5.7 System and Monitoring
|
||||
top=### top
|
||||
swapstat=### swapstat
|
||||
@@ -125,19 +129,33 @@ split=### split
|
||||
spwin=### spwin
|
||||
detach=### detach
|
||||
bkg=### bkg
|
||||
fish-mode-prompt=### fish_mode_prompt
|
||||
fish-prompt=### fish_prompt
|
||||
fish-right-prompt=### fish_right_prompt
|
||||
right-prompt=### fish_right_prompt
|
||||
jobrunner=### jobrunner
|
||||
jr=### jr
|
||||
ssh=### ssh
|
||||
clipboard=## 5.9 Clipboard
|
||||
copy-fn=### y
|
||||
p=### p
|
||||
paste=### paste
|
||||
network=## 5.10 Network
|
||||
fast=### fast
|
||||
fast-cli=### fast-cli
|
||||
speedtest=### fast-cli
|
||||
gip=### gip
|
||||
gip4=### gip4
|
||||
ipv4=### gip4
|
||||
gip6=### gip6
|
||||
ipv6=### gip6
|
||||
ping=### ping
|
||||
qr=### qr
|
||||
logging=## 5.11 Pager and Logging
|
||||
logs=### logs
|
||||
smart-exit=### smart_exit
|
||||
sponge-filter-secrets=### sponge_filter_secrets
|
||||
secret-filter=### sponge_filter_secrets
|
||||
ai=## 5.12 AI and Developer Tools
|
||||
antigravity-ide=### antigravity-ide
|
||||
agy=### agy
|
||||
@@ -152,28 +170,52 @@ devlogs=### agents-init
|
||||
claude-cli=### claude
|
||||
claude-docs=### claude-docs
|
||||
claude-pr=### claude-pr
|
||||
dops=### dops
|
||||
docker-ps=### dops
|
||||
qc=### qc
|
||||
quick-chat=### qc
|
||||
aichat=### qc
|
||||
superpowers=### superpowers
|
||||
media=## 5.13 Media and Utilities
|
||||
dng2avif=### dng2avif
|
||||
play-media=### play-media
|
||||
video=### play-media
|
||||
audio=### play-media
|
||||
mpv=### play-media
|
||||
vlc=### play-media
|
||||
spark=### spark
|
||||
steam-dl=### steam-dl
|
||||
steam=### steam-dl
|
||||
yt-dlp=### yt-dlp
|
||||
miscfns=## 5.14 Miscellaneous
|
||||
bash=### bash
|
||||
bd-pull=### bd-pull
|
||||
beads=### bd-pull
|
||||
cffetch=### cffetch
|
||||
fastfetch=### ffetch
|
||||
neofetch=### cffetch
|
||||
sysinfo=### cffetch
|
||||
cheat=### cheat
|
||||
config-help=### config-help
|
||||
config-update=### config-update
|
||||
config-settings=### config-settings
|
||||
config-toggle=### config-toggle
|
||||
toggle=### config-settings
|
||||
dockup=### dockup
|
||||
fetch=### cffetch
|
||||
ffetch=### ffetch
|
||||
fzf_configure_bindings=### fzf_configure_bindings
|
||||
joplin=### joplin
|
||||
ld=### ld
|
||||
lazydocker=### ld
|
||||
open-url=### open-url
|
||||
url-open=### open-url
|
||||
repo-open=### repo-open
|
||||
open-repo=### repo-open
|
||||
config-update=### config-update
|
||||
config-settings=### config-settings
|
||||
config-toggle=### config-settings
|
||||
toggle=### config-settings
|
||||
bash=### bash
|
||||
cheat=### cheat
|
||||
dockup=### dockup
|
||||
joplin=### joplin
|
||||
rand_string=### rand_string
|
||||
random=### rand_string
|
||||
password=### rand_string
|
||||
randomword=### rand_string
|
||||
replay=### replay
|
||||
tmux=### tmux-clean
|
||||
wake-lock=### wake-lock
|
||||
@@ -208,6 +250,7 @@ pager-hierarchy=## Pager Hierarchy
|
||||
shell-aliases=## 4.11 Shell Aliases
|
||||
kitty-logging=### kitty-logging
|
||||
watcher=### kitty-logging
|
||||
kitty-scrollback=### Scrollback History
|
||||
|
||||
# ── Section 6: Dependency Catalog ────────────────────────────
|
||||
catalog=# 6. DEPENDENCY CATALOG
|
||||
@@ -215,6 +258,8 @@ deps-catalog=# 6. DEPENDENCY CATALOG
|
||||
required=## Required
|
||||
integrations=## Integrations
|
||||
recommended=## Recommended
|
||||
optional-deps=## Optional
|
||||
terminal-emulators=## Terminal Emulators
|
||||
install-methods=## Install Methods
|
||||
|
||||
# ── Section 7: Customization ──────────────────────────────────
|
||||
@@ -231,19 +276,47 @@ minimal=## Opinionated Components (Minimal Mode)
|
||||
minimal-mode=## Opinionated Components (Minimal Mode)
|
||||
opt-out=## Opinionated Components (Minimal Mode)
|
||||
toggles=## Opinionated Components (Minimal Mode)
|
||||
agent-vault=## Agent Memory Vault
|
||||
__fish_agent_vault_dir=## Agent Memory Vault
|
||||
__fish_agent_vault_autopush=## Agent Memory Vault
|
||||
component-reference=# 8. COMPONENTS REFERENCE
|
||||
components=# 8. COMPONENTS REFERENCE
|
||||
c0=## Per-function overrides: `C0`/`always`
|
||||
always-tag=## Per-function overrides: `C0`/`always`
|
||||
c1=## C1 — Command Shadows
|
||||
command-shadows=## C1 — Command Shadows
|
||||
aliases-detail=## C1 — Command Shadows
|
||||
filesystem=### filesystem
|
||||
network-shadow=### network
|
||||
monitor=### monitor
|
||||
shell-tools=### shell-tools
|
||||
dev-tools=### dev-tools
|
||||
c2=## C2 — Startup Side-Effects
|
||||
autoexec=## C2 — Startup Side-Effects
|
||||
startup=## C2 — Startup Side-Effects
|
||||
plugin-management=### plugin-management
|
||||
pkg-wrappers=### pkg-wrappers
|
||||
venv-toggle=### venv
|
||||
telemetry=### telemetry
|
||||
sync=### sync
|
||||
c3=## C3 — Key and Environment Overrides
|
||||
overrides-detail=## C3 — Key and Environment Overrides
|
||||
bang-bang=## C3 — Key and Environment Overrides
|
||||
bindings-toggle=### key-bindings
|
||||
environment=### environment
|
||||
prompt-toggle=### prompt
|
||||
privacy=### privacy
|
||||
privacy-toggle=### privacy
|
||||
do-not-track=### privacy
|
||||
disable-telemetry=### privacy
|
||||
c4=## C4 — Terminal and Tool Integration
|
||||
integrations-detail=## C4 — Terminal and Tool Integration
|
||||
terminal-abbrs=### terminal-abbrs
|
||||
window-mgmt=### window-mgmt
|
||||
window-management=### window-mgmt
|
||||
notifications-toggle=### notifications
|
||||
history-logs=### history-logs
|
||||
pkg-upgrade=### pkg-upgrade
|
||||
c5=## C5 — Logging and Capture
|
||||
logging-detail=## C5 — Logging and Capture
|
||||
logging-sentinel=## C5 — Logging and Capture
|
||||
@@ -252,11 +325,17 @@ zellij-logging=## C5 — Logging and Capture
|
||||
tmux-logging=## C5 — Logging and Capture
|
||||
pipe-pane=## C5 — Logging and Capture
|
||||
dump-screen=## C5 — Logging and Capture
|
||||
terminal-capture=### terminal-capture
|
||||
multiplexer-capture=### multiplexer-capture
|
||||
pkg-logs=### pkg-logs
|
||||
c6=## C6 — Greeting and First-Run UI
|
||||
greeting=## C6 — Greeting and First-Run UI
|
||||
first-run=### first-run
|
||||
greeting-message=### greeting-message
|
||||
|
||||
# ── Prompt and Theme ──────────────────────────────────────────
|
||||
prompt-theme=## Prompt and Theme
|
||||
prompt=## Prompt and Theme
|
||||
starship=### Starship
|
||||
fallback-prompt=### Catppuccin Fallback Prompt
|
||||
catppuccin-prompt=### Catppuccin Fallback Prompt
|
||||
@@ -316,11 +395,31 @@ minimal-trouble=## What's with the C1-C6 stuff?
|
||||
viewing=# 13. VIEWING THIS MANUAL
|
||||
manual=# 13. VIEWING THIS MANUAL
|
||||
ov=## In the terminal
|
||||
man-page=## As a man page
|
||||
manpage=## As a man page
|
||||
jump=## In the terminal
|
||||
html=## The documentation website
|
||||
browser=## The documentation website
|
||||
site=## The documentation website
|
||||
source=## Reading the source directly
|
||||
raw-source=## Reading the source directly
|
||||
|
||||
# ── Section 14: Testing ───────────────────────────────────────
|
||||
testing=# 14. TESTING
|
||||
tests=# 14. TESTING
|
||||
|
||||
# ── Section 15: Contributing ──────────────────────────────────
|
||||
contributing=# 15. CONTRIBUTING
|
||||
contribute=# 15. CONTRIBUTING
|
||||
forge=# 15. CONTRIBUTING
|
||||
|
||||
# ── Section 16: Attribution ───────────────────────────────────
|
||||
attribution=# 16. ATTRIBUTION
|
||||
credits=# 16. ATTRIBUTION
|
||||
|
||||
# ── Section 17: License ───────────────────────────────────────
|
||||
license=# 17. LICENSE
|
||||
licensing=# 17. LICENSE
|
||||
agpl=# 17. LICENSE
|
||||
copyright=# 17. LICENSE
|
||||
|
||||
|
||||
|
||||
+725
-230
File diff suppressed because it is too large
Load Diff
@@ -99,10 +99,16 @@ def render(registry: dict[str, list[str]]) -> str:
|
||||
lines += [f" {k} \\" for k in quoted_keys[:-1]] + [f" {quoted_keys[-1]}"]
|
||||
lines.append("")
|
||||
|
||||
values = ['"' + " ".join(registry[k]) + '"' for k in keys]
|
||||
values = [
|
||||
(
|
||||
'"' + " ".join(registry[k]) + '"'
|
||||
if any(c in " ".join(registry[k]) for c in ' \t*?[]"\'\\$')
|
||||
else " ".join(registry[k])
|
||||
)
|
||||
for k in keys
|
||||
]
|
||||
lines.append("set -g __fish_config_op_registry_values \\")
|
||||
lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"]
|
||||
lines.append("")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
|
||||
@@ -7,51 +7,6 @@ sidebar:
|
||||
order: 4
|
||||
---
|
||||
|
||||
1. Configuration Variables
|
||||
2. PATH Setup
|
||||
3. Key Bindings
|
||||
4. Abbreviations
|
||||
4.1 Editors
|
||||
4.2 Navigation and Listing
|
||||
4.3 Git
|
||||
4.4 Terminal Windows, Tabs, and Panes
|
||||
4.5 Chezmoi
|
||||
4.6 Docker
|
||||
4.7 Systemctl
|
||||
4.8 AI Assistants
|
||||
4.9 History Expansion
|
||||
4.10 Miscellaneous
|
||||
4.11 Shell Aliases
|
||||
5. Functions Reference
|
||||
5.1 File and Directory
|
||||
5.2 Navigation
|
||||
5.3 Editors and Viewers
|
||||
5.4 Git and Version Control
|
||||
5.5 Package Management
|
||||
5.6 Dependency Management
|
||||
5.7 System and Monitoring
|
||||
5.8 Terminal Management
|
||||
5.9 Clipboard
|
||||
5.10 Network
|
||||
5.11 Pager and Logging
|
||||
5.12 AI and Developer Tools
|
||||
5.13 Media and Utilities
|
||||
5.14 Miscellaneous
|
||||
6. Dependency Catalog
|
||||
7. Customization
|
||||
8. Fisher Plugins
|
||||
9. Installation
|
||||
10. Personalization
|
||||
11. Troubleshooting
|
||||
11.1 Uninstalling and Reverting to Backup
|
||||
11.2 Fish Version Requirement
|
||||
11.3 Enable or Disable Session Logging
|
||||
11.4 Change or Disable the Greeting
|
||||
11.5 Secrets and Machine-Local Configuration
|
||||
11.6 Tool Init Does Nothing (Return Sentinel)
|
||||
11.7 Missing Dependencies
|
||||
11.8 Vi Mode Keybindings
|
||||
11.9 What's with the C1-C6 stuff?
|
||||
12. Viewing This Manual
|
||||
<!-- GENERATED: toc -->
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +118,8 @@ Every shell command is reported to WakaTime for time-tracking. Set
|
||||
|
||||
### Tailscale
|
||||
|
||||
Full tab completion for the `tailscale` CLI is provided via `conf.d/tailscale.fish`.
|
||||
Full tab completion for the `tailscale` CLI is provided via
|
||||
`completions/tailscale.fish`, autoloaded on the first `tailscale<TAB>`.
|
||||
|
||||
### Done Notifications
|
||||
|
||||
|
||||
@@ -159,6 +159,27 @@ interactively. See [Components Reference](/08-components-reference/) for the
|
||||
full sub-category breakdown of every category.
|
||||
|
||||
|
||||
## Agent Memory Vault
|
||||
|
||||
__fish_agent_vault_dir
|
||||
|
||||
Overrides the agent memory vault location. Defaults to
|
||||
$XDG_DATA_HOME/agent-vault (or ~/.local/share/agent-vault).
|
||||
|
||||
__fish_agent_vault_autopush
|
||||
|
||||
When set to 1, agents-vault also pushes on wrapper launch. Defaults to
|
||||
off: the vault commits locally on every launch and pushes from the
|
||||
Claude Code SessionEnd hook or an explicit agents-vault --push. That
|
||||
push is synchronous, so with autopush on the pull and the push are
|
||||
each capped at 20 seconds; an explicit --push is left uncapped.
|
||||
|
||||
NOTE:
|
||||
With autopush off and no SessionEnd hook installed, backups accumulate
|
||||
locally and never reach the remote. Run agents-vault --status to check
|
||||
how far ahead the vault is.
|
||||
|
||||
|
||||
## Prompt and Theme
|
||||
|
||||
### Starship
|
||||
@@ -167,11 +188,14 @@ The primary prompt is Starship, initialized by conf.d/starship.fish.
|
||||
Configure it via ~/.config/starship.toml.
|
||||
|
||||
conf.d/starship.fish defines a fish_prompt wrapper that only activates when
|
||||
starship is in PATH. It emits OSC 133;A (prompt start) immediately before
|
||||
Starship renders and OSC 133;B (input start) immediately after, placing both
|
||||
markers on the prompt line itself. This allows ov to use them as sticky
|
||||
section headers when browsing scrollback logs. Without Starship, fish's
|
||||
built-in prompt handles these markers automatically.
|
||||
starship is in PATH and C3 overrides are enabled (see Opinionated
|
||||
Components above). It emits OSC 133;A (prompt start) immediately before
|
||||
Starship renders and OSC 133;B (input start) immediately after, placing
|
||||
both markers on the prompt line itself. This allows ov to use them as
|
||||
sticky section headers when browsing scrollback logs. It also prints a
|
||||
blank line before the prompt, skipped in private mode or on a freshly
|
||||
cleared screen. Without Starship, fish's built-in prompt handles these
|
||||
markers automatically.
|
||||
|
||||
### Catppuccin Fallback Prompt
|
||||
|
||||
@@ -179,47 +203,84 @@ When Starship is absent or C3 overrides are disabled, a built-in nim-style
|
||||
two-line prompt activates from functions/fish_prompt.fish. No external
|
||||
dependencies — fish builtins only.
|
||||
|
||||
Layout:
|
||||
Layout (a dim job line appears between the two rows for each running
|
||||
background job):
|
||||
|
||||
┬─[user@host:~/path] (main)
|
||||
│ nvim notes.md
|
||||
╰─>$
|
||||
|
||||
Elements:
|
||||
|
||||
user Yellow (Catppuccin Yellow); red if root
|
||||
@host Blue (local) or Teal (SSH)
|
||||
~/path prompt_pwd abbreviation (Catppuccin Text)
|
||||
(main) Current git branch in Catppuccin Pink; omitted outside repos
|
||||
─[V:name] Active Python venv basename; omitted when none
|
||||
─[N/I/R/V] Vi-mode indicator when vi bindings are active
|
||||
┬─ / ╰─> Connector lines: Catppuccin Green on success, Red on failure
|
||||
Segment Meaning
|
||||
────────────────────────────────────────────────────────────────
|
||||
user Yellow (Catppuccin Yellow); red if root
|
||||
@host Blue (local) or Teal (SSH)
|
||||
~/path prompt_pwd abbreviation (Catppuccin Text)
|
||||
─[N/I/R/V/O] Vi-mode indicator (Normal/Insert/Replace/Visual/Operator);
|
||||
shown only when vi or hybrid key bindings are active
|
||||
─[V:name] Active Python venv basename; omitted when none
|
||||
(main) Current git branch in Catppuccin Pink, with ↑/↓
|
||||
upstream-tracking arrows when applicable;
|
||||
omitted outside repos
|
||||
┬─ / ╰─> Connector lines: Catppuccin Green on success,
|
||||
Red on failure
|
||||
|
||||
The right prompt (fish_right_prompt.fish) always renders, regardless of C3
|
||||
state. On failure it shows a red ✘ and the exit code; on success it shows
|
||||
only the dim timestamp. When starship is installed and C3 is enabled, the
|
||||
active Docker context is also shown (if non-default):
|
||||
The right prompt (fish_right_prompt.fish) always renders, independently of
|
||||
which left prompt is active:
|
||||
|
||||
✘ 1 myctx Fri Jun 12 00:51:21 2026 ← failed, starship+C3 active
|
||||
✘ 1 Fri Jun 12 00:51:21 2026 ← failed, fallback prompt
|
||||
Fri Jun 12 00:51:21 2026 ← success (no ✘)
|
||||
Segment Shown when
|
||||
────────────────────────────────────────────────────────────────
|
||||
✘ <code> The previous command exited non-zero (red)
|
||||
<context> docker and starship are both installed, C3
|
||||
overrides are enabled, and the active Docker
|
||||
context is set and non-default
|
||||
<timestamp> Always (dim, Catppuccin Overlay0)
|
||||
|
||||
The exit-status and Docker segments are independent — for example, right
|
||||
after a failing command with a non-default Docker context active:
|
||||
|
||||
✘ 1 myctx Fri Jun 12 00:51:21 2026
|
||||
|
||||
A successful command with the same Docker context shows the segment too:
|
||||
|
||||
myctx Fri Jun 12 00:51:21 2026
|
||||
|
||||
And without Starship (or with C3 disabled, or Docker not installed), only
|
||||
the exit-status prefix and timestamp ever appear:
|
||||
|
||||
✘ 1 Fri Jun 12 00:51:21 2026
|
||||
|
||||
### FZF
|
||||
|
||||
FZF is themed to Catppuccin Mocha via FZF_DEFAULT_OPTS set in
|
||||
integrations/fzf.fish. The colors applied:
|
||||
FZF is themed to Catppuccin Mocha via FZF_DEFAULT_OPTS, set in
|
||||
conf.d/theme.fish (opinionated; disabled by `__fish_config_op_overrides`,
|
||||
see Opinionated Components above). The colors applied:
|
||||
|
||||
Background: #1E1E2E (base) #313244 (surface0)
|
||||
Foreground: #CDD6F4 (text)
|
||||
Highlights: #F38BA8 (red) #CBA6F7 (mauve) #B4BEFE (lavender)
|
||||
Hex Role Catppuccin name
|
||||
────────────────────────────────────────────────────────
|
||||
#1E1E2E Background Base
|
||||
#313244 Highlighted background Surface0
|
||||
#45475A Selected background Surface1
|
||||
#CDD6F4 Foreground Text
|
||||
#F38BA8 Highlight / header Red
|
||||
#CBA6F7 Info / prompt Mauve
|
||||
#B4BEFE Marker Lavender
|
||||
#F5E0DC Spinner / pointer Rosewater
|
||||
#6C7086 Border Overlay0
|
||||
|
||||
To customize, override FZF_DEFAULT_OPTS in local.fish.
|
||||
To customize, override FZF_DEFAULT_OPTS in local.fish — it is sourced after
|
||||
conf.d/theme.fish on every session, so a `set -Ux FZF_DEFAULT_OPTS ...`
|
||||
there always wins.
|
||||
|
||||
### Catppuccin Mocha Syntax Highlighting
|
||||
|
||||
The Catppuccin Mocha theme ships with this config in themes/ and is applied
|
||||
on first run via `conf.d/first_run.fish`. Colors are stored in fish_variables
|
||||
(universal). To switch variants, install a different theme from themes/:
|
||||
automatically on first run via `conf.d/first_run.fish` (gated by
|
||||
`__fish_config_op_autoexec`; see Opinionated Components above). Colors are
|
||||
stored in fish_variables (universal). Three other bundled variants are
|
||||
available in themes/ — Latte, Frappé, and Macchiato. To switch:
|
||||
|
||||
fish_config theme save "Catppuccin Latte"
|
||||
fish_config theme choose "Catppuccin Latte"
|
||||
|
||||
`---`
|
||||
---
|
||||
|
||||
@@ -24,6 +24,8 @@ all of them.
|
||||
Catppuccin colors 30+ fish_color_* variables set to Mocha palette
|
||||
FZF_DEFAULT_OPTS FZF themed to Catppuccin Mocha colors
|
||||
Right prompt fish_right_prompt: exit code (on failure) + dim timestamp; always rendered; Docker context added when starship+C3 active
|
||||
DO_NOT_TRACK=1 Universal telemetry opt-out for tools and AI agents
|
||||
DISABLE_TELEMETRY=1 Telemetry opt-out for telemetry-aware CLIs
|
||||
|
||||
The bang-bang system spans `key_bindings.fish`, `abbr.fish`, `puffer.fish`, and
|
||||
six `expand_bang_*.fish` functions. All are gated together — disabling C3
|
||||
@@ -35,7 +37,7 @@ is independently controlled by C5 (see below).
|
||||
|
||||
## Sub-categories
|
||||
|
||||
`__fish_config_op_overrides` sub-divides into three sub-categories, each
|
||||
`__fish_config_op_overrides` sub-divides into four sub-categories, each
|
||||
with its own `__fish_config_op_overrides_<slug>` toggle:
|
||||
|
||||
## key-bindings
|
||||
@@ -53,3 +55,8 @@ Starship, the right prompt, Catppuccin syntax/prompt colors, and FZF
|
||||
theming (`$FZF_DEFAULT_OPTS`) -- all driven by the same guard as a single
|
||||
unit, not independently toggleable from each other.
|
||||
|
||||
## privacy
|
||||
|
||||
`$DO_NOT_TRACK` and `$DISABLE_TELEMETRY` environment variables for
|
||||
telemetry opt-out across CLI tools, runtimes, and AI agents.
|
||||
|
||||
|
||||
@@ -18,16 +18,16 @@ CAUTION: This configuration is capable of silently recording terminal output and
|
||||
Component What it captures
|
||||
───────────────────────────────────────────────────────────────────────────
|
||||
Scrollback capture Terminal session output saved to:
|
||||
`~/.terminal_history/scrollback_YYYY-MM-DD_HH-MM-SS.log`
|
||||
~/.terminal_history/scrollback_YYYY-MM-DD_HH-MM-SS.log
|
||||
tmux pane capture Continuous pane stream via pipe-pane, saved to:
|
||||
`~/.terminal_history/tmux_<session>-w<win>-p<pane>_YYYY-MM-DD_HH-MM-SS.log`
|
||||
~/.terminal_history/tmux_<session>-w<win>-p<pane>_YYYY-MM-DD_HH-MM-SS.log
|
||||
zellij pane capture Pane scrollback snapshot on shell exit, saved to:
|
||||
`~/.terminal_history/zellij_<session>-p<pane>_YYYY-MM-DD_HH-MM-SS.log`
|
||||
~/.terminal_history/zellij_<session>-p<pane>_YYYY-MM-DD_HH-MM-SS.log
|
||||
paru wrapper All paru/AUR output captured to:
|
||||
`~/.terminal_history/paru_YYYY-MM-DD_HH-MM-SS.log`
|
||||
~/.terminal_history/paru_YYYY-MM-DD_HH-MM-SS.log
|
||||
yay wrapper All yay/AUR output captured to:
|
||||
`~/.terminal_history/yay_YYYY-MM-DD_HH-MM-SS.log`
|
||||
Kitty watcher `watcher.py` captures scrollback when Kitty closes
|
||||
~/.terminal_history/yay_YYYY-MM-DD_HH-MM-SS.log
|
||||
Kitty watcher watcher.py captures scrollback when Kitty closes
|
||||
|
||||
NOTE: **Turning off logging does not delete any existing logs.**
|
||||
They remain in `$SCROLLBACK_HISTORY_DIR` (defaults to: `~/.terminal_history/`)
|
||||
@@ -58,7 +58,7 @@ Ctrl-D, or a logout), because that is when the fish_exit handler runs. It does
|
||||
NOT capture when you close a pane or quit zellij through zellij itself:
|
||||
|
||||
- Closing a pane signals the shell and tears the pane down concurrently, so
|
||||
even if the handler runs, `dump-screen` may find the pane buffer already
|
||||
even if the handler runs, dump-screen may find the pane buffer already
|
||||
gone.
|
||||
- Quitting zellij kills the zellij server, and `dump-screen` needs a live
|
||||
server to read from — there is nothing left to snapshot.
|
||||
@@ -93,7 +93,7 @@ start, so it appears without any action on your part.
|
||||
Disabling `__fish_config_op_logging` (or leaving it unset):
|
||||
1. Creates the sentinel immediately in every open shell.
|
||||
2. Removes `~/.local/bin/paru` and `~/.local/bin/yay` logging wrappers;
|
||||
bare `/usr/bin/paru` and `/usr/bin/yay` are used instead.
|
||||
bare /usr/bin/paru and /usr/bin/yay are used instead.
|
||||
3. Kitty's `watcher.py` reads the sentinel on each save attempt and
|
||||
skips capture — no Kitty restart required.
|
||||
4. smart_exit stops saving scrollback logs.
|
||||
|
||||
@@ -12,16 +12,16 @@ category variable.
|
||||
|
||||
Category Description
|
||||
──────────────────────────────────────────────────────────────────────────
|
||||
C1 [Command Shadows](/08-components-reference/01-c1-command-shadows/) — Wraps destructive commands (`rm`, `cp`) to be safe by default
|
||||
C1 [Command Shadows](/08-components-reference/01-c1-command-shadows/) — Wraps destructive commands (rm, cp) to be safe by default
|
||||
C2 [Startup Side-Effects](/08-components-reference/02-c2-startup-side-effects/) — Bootstraps Fisher, generates wrappers, auto-activates venvs
|
||||
C3 [Overrides](/08-components-reference/03-c3-key-and-environment-overrides/) — Overrides `cd`, sets Vi mode, binds `<CR>` to `smart_enter`
|
||||
C3 [Overrides](/08-components-reference/03-c3-key-and-environment-overrides/) — Overrides cd, sets Vi mode, binds <CR> to smart_enter
|
||||
C4 [Integrations](/08-components-reference/04-c4-terminal-and-tool-integration/) — Kitty/Wezterm integrations, starship hooks, fzf theme
|
||||
C5 [Logging and Capture](/08-components-reference/05-c5-logging-and-capture/) — Session logs, command duration
|
||||
C6 [Greeting & First-Run UI](/08-components-reference/06-c6-greeting-and-first-run-ui/) — Custom startup banner
|
||||
|
||||
Each category further sub-divides into two to six sub-categories (24 in
|
||||
total) with their own `__fish_config_op_<category>_<subcategory>` toggles
|
||||
-- see that category's page for its sub-category list.
|
||||
Each category further sub-divides into several sub-categories, each with
|
||||
its own `__fish_config_op_<category>_<subcategory>` toggle -- see that
|
||||
category's page for its sub-category list.
|
||||
|
||||
## Per-function overrides: `C0`/`always`
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ The `fish_plugins` file at the config root:
|
||||
- [`jorgebucaran/fisher`](https://github.com/jorgebucaran/fisher) — Plugin manager itself
|
||||
- [`meaningful-ooo/sponge`](https://github.com/meaningful-ooo/sponge) — Remove failed commands from history
|
||||
|
||||
To update all Fisher-managed plugins, run `fisher update` or `fish-deps
|
||||
update` which calls it as its first step.
|
||||
To update all Fisher-managed plugins, run `fisher update` or
|
||||
`fish-deps update` which calls it as its first step.
|
||||
|
||||
---
|
||||
|
||||
@@ -208,9 +208,9 @@ This configuration groups its opinionated behaviors into six categories (C1–C6
|
||||
|
||||
Category Description
|
||||
──────────────────────────────────────────────────────────────────────────
|
||||
C1 [Command Shadows](/08-components-reference/01-c1-command-shadows/) — Wraps destructive commands (`rm`, `cp`) to be safe by default
|
||||
C1 [Command Shadows](/08-components-reference/01-c1-command-shadows/) — Wraps destructive commands (rm, cp) to be safe by default
|
||||
C2 [Startup Side-Effects](/08-components-reference/02-c2-startup-side-effects/) — Bootstraps Fisher, generates wrappers, auto-activates venvs
|
||||
C3 [Overrides](/08-components-reference/03-c3-key-and-environment-overrides/) — Overrides `cd`, sets Vi mode, binds `<CR>` to `smart_enter`
|
||||
C3 [Overrides](/08-components-reference/03-c3-key-and-environment-overrides/) — Overrides cd, sets Vi mode, binds <CR> to smart_enter
|
||||
C4 [Integrations](/08-components-reference/04-c4-terminal-and-tool-integration/) — Kitty/Wezterm integrations, starship hooks, fzf theme
|
||||
C5 [Logging and Capture](/08-components-reference/05-c5-logging-and-capture/) — Session logs, command duration
|
||||
C6 [Greeting & First-Run UI](/08-components-reference/06-c6-greeting-and-first-run-ui/) — Custom startup banner
|
||||
@@ -237,7 +237,7 @@ Re-enable everything:
|
||||
|
||||
set -Ue __fish_config_opinionated
|
||||
|
||||
Each category also has two to six sub-categories (e.g.
|
||||
Each category can also have several sub-categories (e.g.
|
||||
`__fish_config_op_aliases_filesystem`) that can be checked, disabled, or
|
||||
reset the same way — `set -U __fish_config_op_<category>_<subcategory> off`
|
||||
and `set -Ue __fish_config_op_<category>_<subcategory>` work identically to
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: Testing
|
||||
manTitle: 14. TESTING
|
||||
sidebar:
|
||||
order: 18
|
||||
helpKeywords:
|
||||
- testing
|
||||
- tests
|
||||
- test-suite
|
||||
- run-tests
|
||||
---
|
||||
|
||||
<!-- README: Testing -->
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Contributing
|
||||
manTitle: 15. CONTRIBUTING
|
||||
sidebar:
|
||||
order: 19
|
||||
helpKeywords:
|
||||
- contributing
|
||||
- contribute
|
||||
- pull-request
|
||||
- fork
|
||||
- issues
|
||||
- forge
|
||||
---
|
||||
|
||||
<!-- README: Contributing -->
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Attribution
|
||||
manTitle: 16. ATTRIBUTION
|
||||
sidebar:
|
||||
order: 20
|
||||
helpKeywords:
|
||||
- attribution
|
||||
- credits
|
||||
- zoxide
|
||||
---
|
||||
|
||||
<!-- README: Attribution -->
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: License
|
||||
manTitle: 17. LICENSE
|
||||
sidebar:
|
||||
order: 21
|
||||
helpKeywords:
|
||||
- license
|
||||
- licensing
|
||||
- agpl
|
||||
- copyright
|
||||
---
|
||||
|
||||
<!-- README: License -->
|
||||
@@ -35,7 +35,6 @@ The configuration uses a structured file tree:
|
||||
├── conf.d/
|
||||
│ ├── abbr.fish All abbreviations
|
||||
│ ├── autopair.fish Auto-pair brackets and quotes
|
||||
│ ├── cheat.fish cheat.sh tab completions
|
||||
│ ├── done.fish Desktop notifications for long commands
|
||||
│ ├── first_run.fish One-time init: Fisher bootstrap, theme
|
||||
│ ├── key_bindings.fish Custom key bindings and Vi mode
|
||||
@@ -47,14 +46,13 @@ The configuration uses a structured file tree:
|
||||
│ ├── zellij-logging.fish C5 fish_exit handler for zellij
|
||||
│ ├── sponge_privacy.fish Sponge privacy patterns
|
||||
│ ├── starship.fish fish_prompt shell-integration markers
|
||||
│ ├── tailscale.fish Tailscale CLI tab completions
|
||||
│ ├── theme.fish Catppuccin syntax highlight colors
|
||||
│ ├── tricks.fish PATH, bang-bang helpers, bat man pages
|
||||
│ ├── wakatime.fish WakaTime shell hook
|
||||
│ ├── yay-wrapper.fish Auto-generates yay logging wrapper
|
||||
│ └── zoxide.fish Zoxide z/zi integration; overrides cd
|
||||
├── functions/ Custom functions, one per file
|
||||
├── completions/ Tab completion scripts
|
||||
├── completions/ Tab completion scripts, autoloaded on demand
|
||||
├── integrations/
|
||||
│ └── fzf.fish FZF Catppuccin theme and key bindings
|
||||
├── scripts/
|
||||
|
||||
@@ -20,6 +20,41 @@ python3 docs/build-manual.py --site
|
||||
`docs/verify-manual.py` validates both sources before you build; run it
|
||||
first if you've touched a header or a manual page.
|
||||
|
||||
## Inline code spans
|
||||
|
||||
Function headers are read as plain text (by `config-help`, by `funcsave`,
|
||||
by anyone opening the `.fish` file), so they're authored without backticks
|
||||
— `-a/--all`, not `` `-a`/`--all` ``. `docs/codespans.py` puts the
|
||||
backticks on at render time, as the last step of `prettify()`.
|
||||
|
||||
`build_concat()` runs the same pass, so the man page and `config-help`
|
||||
mark code the way the site does rather than only where the SSOT happened
|
||||
to backtick something by hand. `config-help` then renders those spans
|
||||
bold and drops the delimiters, since a terminal pager would otherwise
|
||||
show them as literal punctuation.
|
||||
|
||||
It recognises flags, `$vars`, `SCREAMING_SNAKE` env vars, snake_case
|
||||
identifiers (`__fish_config_op_aliases`, `fish_greeting`), paths and
|
||||
filenames, key chords (`Ctrl-R`), shadow chains (`ls->eza`), runs of tool
|
||||
names (`btop, dust, duf, …`), whole command lines in a table column of
|
||||
command lines, and command names it knows — the `_fdc_*` catalog in
|
||||
`functions/_fish_deps_catalog.fish`, the `functions/` directory listing,
|
||||
and a standard-command list in the module.
|
||||
|
||||
Names that also read as English (`find`, `top`, `screen`) are listed in
|
||||
`AMBIGUOUS_COMMANDS` and are never wrapped on sight; they still count
|
||||
where position already proves they're a command. Add to that list rather
|
||||
than removing a rule if a wrap ever reads wrong.
|
||||
|
||||
Fenced blocks, indented blocks, existing code spans, headings, link
|
||||
targets, URLs, component markup, and `<FileTree>` bodies are never
|
||||
touched. Leaving a token alone is always the safe outcome, so every rule
|
||||
bails out when it isn't sure.
|
||||
|
||||
Indented blocks matter only to the concat — `prettify()` has already
|
||||
fenced them by the time the site is rendered — but there they are the
|
||||
table of contents and every section 5 entry, which must stay verbatim.
|
||||
|
||||
## llms.txt
|
||||
|
||||
The [`starlight-llms-txt`](https://www.npmjs.com/package/starlight-llms-txt)
|
||||
|
||||
@@ -28,6 +28,11 @@ export default defineConfig({
|
||||
label: 'Gitea',
|
||||
href: 'https://git.rootiest.dev/rootiest/fish-config',
|
||||
},
|
||||
{
|
||||
icon: 'github',
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/rootiest/fish-config',
|
||||
},
|
||||
],
|
||||
components: {
|
||||
SocialIcons: './src/components/starlight/SocialIcons.astro',
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
<defs>
|
||||
<style id="current-color-scheme" type="text/css">
|
||||
.ColorScheme-Accent{color:#ff004c}
|
||||
.ColorScheme-Text{color:#ff004c}
|
||||
</style>
|
||||
</defs>
|
||||
<path fill="currentColor" d="M1595.4 0c2.1 3.3 7.6 2 11 2 184 1.9 408.1 39.6 576.4 115.6 21.9 9.9 41.4 24.3 62.6 35.4 7.5 3.9 31.4 10.3 32.1 18.1.3 3.1-1.4 6.6-4.1 8-4.9 2.5-41.8-13-50.4-15.6-43.5-13.4-89.2-27.2-133.1-38.9-37.5-10-79-23.5-117.6-16.7-6.4 15 20.6 16.7 24.2 22.1s2.5 6.9-1.8 5.9c-25.1-5.8-61.6-32.7-87.9-44.1-181.7-79.1-436.4-32-621.8 18.9-62.2 17.1-134.7 38.6-193.5 64.5-6.5 2.8-26.5 11.6-30.8 15.2-5.8 4.7-6.3 15.3 0 19.6s21.8 1.6 29.8 2.2c60.4 4.1 109.5-1.3 169.9 14.1 32.2 8.2 75.3 25 99 49 13.2 13.4-9.9 11.2-18.8 10.8-26.4-1.1-53.4-6.9-79.9-8.1-359.5-15.3-709.4 225.7-883.4 528.7-39.7 69.1-86 168.1-104.7 245.3-2.5 10.2-4.8 30.5 11.9 28.1 4.1-.6 27.2-26.6 32.1-32 19-21 35.6-44.5 54.9-65.1 49.2-52.5 133-106.5 200.7-131.3 9.8-3.6 21.1-7.4 29.3 1.5 1.2 5.4-65.6 70-75 80.8-150.4 171.5-255.6 462.1-269.1 688.9-2 33.9-3.9 58.7-7.7 92.3-5 45.3.9 103.6 6 149.5 15.1 137.1 41.9 256.6 107.4 378.5 31.1 57.9 71.4 117.9 123.4 158.6.2-44.3-9.3-88.7-13-133-11.8-142 20.4-314.7 69.5-448.5.6-1.7-.2-6.2 3.5-4.4 8.6 4.1 6.2 25.5 7 33 5.2 43.5 4.9 86.4 8 130 21.2 296.4 113.7 587.3 363.3 766.6 117.3 84.3 282 143.6 422.6 175.4 48.2 10.9 110.9 26.2 159.3 30.7 9.9.9 28.4 2.4 16.8-10.8s-51.6-26.9-68.1-35.9c-81.7-45-140-102.4-203.9-168.1-5.7-5.8-36.5-28.3-30-36.9 82.5 57.3 163.1 113.7 258.4 148.5 234.9 85.7 522.2 75 731-70.1 81.2-56.5 139.1-110 198.5-189.5 11.7-15.6 43.6-53 17-66.9-81.7 3.4-160.6-6.1-238-32-32.1-10.8-72.2-25.3-102.9-39.1s-40.9-19.2-27.1-20.9c80.7 2.5 170.3-9.8 242.3-47.6 13-6.8 58-35.1 63.1-46.8 8.7-20-12.2-13.7-21.5-13.5-67 1.2-135.7 14-202 12-87.1-2.7-190.5-19.5-271-53-96.7-40.3-177.4-108.6-256.2-175.9-4.2-6.2 4.1-14.8 11-13 36.7 14.2 75.8 21.2 114.5 27.7 86.4 14.3 178 24.9 264.3 5 5-1.2 29.6-6.3 30.4-9.7-8.6-15.9-21.4-20.4-37.9-24-81.9-18-164.5-27.7-246.9-49.1-154.8-40.2-344-122.2-426-267.9-5.7-10.2-18.2-35.6 5.6-23.6 14.8 7.5 37.4 29 54.7 39.3 117.7 70.4 312.2 80.7 444.7 55.4 13-2.5 64.8-9.1 41-29.1s-56.3-29.3-71.7-36.3c-179.8-82-393.1-100.3-578.4-165.6-233-82.1-413.3-209.7-545.8-420.2-26.4-41.9-48-87.4-65.2-133.8 10.7.2 11 12 16 19 14.5 20.1 69.2 63.3 91 81 281.1 227.1 683.7 237.1 1020.9 333 80.4 22.9 187.3 55.5 261.4 92.6 16.9 8.4 32 20.5 48.6 29.4 58.2 31 104.9 55.4 158.7 95.3 35.2 26.1 64.9 60.7 106.9 75.1 97.7 33.4 169.4 16.3 265.5 24.5 37.3 3.2 11.7 26.7 3.2 45.3-48.7 106.4-25.7 198.6 32.9 294.6 38.9 63.7 125.6 144.1 206.9 129.2 74.9-13.7 115.4-158 88.3-220.5s-28-33.5-46.2-25.4c-26.8 11.9-51.8 78.2-53.2 106.8-1.8 3-5.6 5.6-9.1 3.1-2.1-3-3.2-6.7-3.7-10.3-6.5-46.6 1.8-85.2 3.9-129.7.9-19.5-2.9-24.7-23.1-23.1-14 1.1-49.1 22.9-50.4.7 32.1-101.4 57-212.9 120.9-300.1 57.4-78.4 218.2-105.9 308.9-139 40.5-14.8 81.1-31.5 120.2-49.8 7.4-3.4 34.8-19.9 39.4-18.9s4.4 3.2 5.9 5.2v8c-26.1 37.3-53.7 70.8-76.3 110.6-52.8 92.8-89.8 196.9-140.6 291.4-35.6 66.1-51.1 76.5-59 157-31.1 314.5-173.9 594.9-419.1 794.8-212.4 173.2-477.4 270.8-750 298l-133 10c-35.9-.9-72.1 1.2-108 0-397.6-12.9-799.2-153.9-1100.9-414C4.9 2277.1-145.4 1511.2 147.7 877.2 251.2 653.3 437.2 437.1 640 297.5 932.6 96.1 1223.4 8.1 1579.4 0z" class="ColorScheme-Accent"/>
|
||||
<path fill="currentColor" class="ColorScheme-Accent" d="M1595.4 0c2.1 3.3 7.6 2 11 2 184 1.9 408.1 39.6 576.4 115.6 21.9 9.9 41.4 24.3 62.6 35.4 7.5 3.9 31.4 10.3 32.1 18.1.3 3.1-1.4 6.6-4.1 8-4.9 2.5-41.8-13-50.4-15.6-43.5-13.4-89.2-27.2-133.1-38.9-37.5-10-79-23.5-117.6-16.7-6.4 15 20.6 16.7 24.2 22.1s2.5 6.9-1.8 5.9c-25.1-5.8-61.6-32.7-87.9-44.1-181.7-79.1-436.4-32-621.8 18.9-62.2 17.1-134.7 38.6-193.5 64.5-6.5 2.8-26.5 11.6-30.8 15.2-5.8 4.7-6.3 15.3 0 19.6s21.8 1.6 29.8 2.2c60.4 4.1 109.5-1.3 169.9 14.1 32.2 8.2 75.3 25 99 49 13.2 13.4-9.9 11.2-18.8 10.8-26.4-1.1-53.4-6.9-79.9-8.1-359.5-15.3-709.4 225.7-883.4 528.7-39.7 69.1-86 168.1-104.7 245.3-2.5 10.2-4.8 30.5 11.9 28.1 4.1-.6 27.2-26.6 32.1-32 19-21 35.6-44.5 54.9-65.1 49.2-52.5 133-106.5 200.7-131.3 9.8-3.6 21.1-7.4 29.3 1.5 1.2 5.4-65.6 70-75 80.8-150.4 171.5-255.6 462.1-269.1 688.9-2 33.9-3.9 58.7-7.7 92.3-5 45.3.9 103.6 6 149.5 15.1 137.1 41.9 256.6 107.4 378.5 31.1 57.9 71.4 117.9 123.4 158.6.2-44.3-9.3-88.7-13-133-11.8-142 20.4-314.7 69.5-448.5.6-1.7-.2-6.2 3.5-4.4 8.6 4.1 6.2 25.5 7 33 5.2 43.5 4.9 86.4 8 130 21.2 296.4 113.7 587.3 363.3 766.6 117.3 84.3 282 143.6 422.6 175.4 48.2 10.9 110.9 26.2 159.3 30.7 9.9.9 28.4 2.4 16.8-10.8s-51.6-26.9-68.1-35.9c-81.7-45-140-102.4-203.9-168.1-5.7-5.8-36.5-28.3-30-36.9 82.5 57.3 163.1 113.7 258.4 148.5 234.9 85.7 522.2 75 731-70.1 81.2-56.5 139.1-110 198.5-189.5 11.7-15.6 43.6-53 17-66.9-81.7 3.4-160.6-6.1-238-32-32.1-10.8-72.2-25.3-102.9-39.1s-40.9-19.2-27.1-20.9c80.7 2.5 170.3-9.8 242.3-47.6 13-6.8 58-35.1 63.1-46.8 8.7-20-12.2-13.7-21.5-13.5-67 1.2-135.7 14-202 12-87.1-2.7-190.5-19.5-271-53-96.7-40.3-177.4-108.6-256.2-175.9-4.2-6.2 4.1-14.8 11-13 36.7 14.2 75.8 21.2 114.5 27.7 86.4 14.3 178 24.9 264.3 5 5-1.2 29.6-6.3 30.4-9.7-8.6-15.9-21.4-20.4-37.9-24-81.9-18-164.5-27.7-246.9-49.1-154.8-40.2-344-122.2-426-267.9-5.7-10.2-18.2-35.6 5.6-23.6 14.8 7.5 37.4 29 54.7 39.3 117.7 70.4 312.2 80.7 444.7 55.4 13-2.5 64.8-9.1 41-29.1s-56.3-29.3-71.7-36.3c-179.8-82-393.1-100.3-578.4-165.6-233-82.1-413.3-209.7-545.8-420.2-26.4-41.9-48-87.4-65.2-133.8 10.7.2 11 12 16 19 14.5 20.1 69.2 63.3 91 81 281.1 227.1 683.7 237.1 1020.9 333 80.4 22.9 187.3 55.5 261.4 92.6 16.9 8.4 32 20.5 48.6 29.4 58.2 31 104.9 55.4 158.7 95.3 35.2 26.1 64.9 60.7 106.9 75.1 97.7 33.4 169.4 16.3 265.5 24.5 37.3 3.2 11.7 26.7 3.2 45.3-48.7 106.4-25.7 198.6 32.9 294.6 38.9 63.7 125.6 144.1 206.9 129.2 74.9-13.7 115.4-158 88.3-220.5s-28-33.5-46.2-25.4c-26.8 11.9-51.8 78.2-53.2 106.8-1.8 3-5.6 5.6-9.1 3.1-2.1-3-3.2-6.7-3.7-10.3-6.5-46.6 1.8-85.2 3.9-129.7.9-19.5-2.9-24.7-23.1-23.1-14 1.1-49.1 22.9-50.4.7 32.1-101.4 57-212.9 120.9-300.1 57.4-78.4 218.2-105.9 308.9-139 40.5-14.8 81.1-31.5 120.2-49.8 7.4-3.4 34.8-19.9 39.4-18.9s4.4 3.2 5.9 5.2v8c-26.1 37.3-53.7 70.8-76.3 110.6-52.8 92.8-89.8 196.9-140.6 291.4-35.6 66.1-51.1 76.5-59 157-31.1 314.5-173.9 594.9-419.1 794.8-212.4 173.2-477.4 270.8-750 298l-133 10c-35.9-.9-72.1 1.2-108 0-397.6-12.9-799.2-153.9-1100.9-414C4.9 2277.1-145.4 1511.2 147.7 877.2 251.2 653.3 437.2 437.1 640 297.5 932.6 96.1 1223.4 8.1 1579.4 0z"/>
|
||||
<g transform="translate(907.9791817150754, 326.150186646207) scale(105.39115723844282, 105.39115723844282)">
|
||||
<path fill="currentColor" class="ColorScheme-Text" d="M9.366 2.085C9.773 2.973 10 3.96 10 5s-.227 2.027-.634 2.915C12.104 7.567 14 6.215 14 5s-1.896-2.567-4.634-2.915zM15 2.577c1.052-1.184 2.851-2.074 5-2.42v9.685c-2.149-.345-3.948-1.235-5-2.42C13.635 8.96 11.012 10 8 10c-4.418 0-8-2.239-8-5s3.582-5 8-5c3.012 0 5.635 1.04 7 2.577zM18 7V3c-1.15.23-2 1.038-2 2s.85 1.77 2 2zM2 5c0 1.27 2.077 2.696 5.032 2.958A4.973 4.973 0 0 0 8 5a4.973 4.973 0 0 0-.968-2.958C4.077 2.304 2 3.73 2 5zm3 1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -20,7 +20,7 @@ const links = config.social || [];
|
||||
return (
|
||||
<a href={href} rel="me" class="sl-flex">
|
||||
<span class="sr-only">{label}</span>
|
||||
{customIcon ? <span class={`social-icon ${customIcon}`} aria-hidden="true" /> : <Icon name={icon} />}
|
||||
{customIcon ? <span class={`social-icon ${customIcon}`} aria-hidden="true" /> : <Icon name={icon} size="1.5em" />}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
@@ -39,8 +39,12 @@ const links = config.social || [];
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
.social-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
/* !important: the i-pajamas:* class comes from UnoCSS, which emits
|
||||
unlayered CSS. Unlayered rules always win over anything in a
|
||||
@layer regardless of specificity or source order, so a plain
|
||||
override here is silently ignored no matter how it's written. */
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
+307
-6
@@ -9,6 +9,7 @@ import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import codespans
|
||||
import manualtools as mt
|
||||
|
||||
# docs/build-manual.py follows this repo's hyphenated CLI-script naming
|
||||
@@ -470,8 +471,8 @@ def test_prettify_splits_an_entry_block():
|
||||
"option table was not converted to a markdown table"
|
||||
)
|
||||
assert (
|
||||
"\nFalls back to /usr/bin/rm when trash is unavailable." in out
|
||||
), "trailing prose stayed indented"
|
||||
"\nFalls back to `/usr/bin/rm` when trash is unavailable." in out
|
||||
), "trailing prose stayed indented (or lost its path code span)"
|
||||
|
||||
|
||||
def test_as_table_converts_option_blocks():
|
||||
@@ -626,19 +627,42 @@ def test_as_ruled_table_rejects_ambiguous_columns():
|
||||
|
||||
|
||||
def test_prettify_leaves_reference_tables_alone():
|
||||
"""Column-aligned blocks are data, not shell, and must not be fenced."""
|
||||
"""Column-aligned blocks too small for a real table are data, not shell,
|
||||
and must not get shell syntax highlighting — but they still need SOME
|
||||
fence, since indentation alone doesn't survive MDX (see
|
||||
test_prettify_fallback_fences_instead_of_indenting)."""
|
||||
import build_manual
|
||||
|
||||
table = " XDG_CONFIG_HOME ~/.config\n XDG_CACHE_HOME ~/.cache"
|
||||
assert "```" not in build_manual.prettify(table), "a reference table got fenced"
|
||||
out = build_manual.prettify(table)
|
||||
assert "```fish" not in out, f"a reference table got shell-highlighted:\n{out}"
|
||||
assert "```text" in out, f"a reference table lost its fence:\n{out}"
|
||||
|
||||
binds = " n / nv / neovim nvim\n e edit"
|
||||
assert "```" not in build_manual.prettify(binds), "an abbreviation table got fenced"
|
||||
out = build_manual.prettify(binds)
|
||||
assert "```fish" not in out, f"an abbreviation table got shell-highlighted:\n{out}"
|
||||
assert "```text" in out, f"an abbreviation table lost its fence:\n{out}"
|
||||
|
||||
shell = " set -U __fish_user_dots_path /path/to/dots"
|
||||
assert "```fish" in build_manual.prettify(shell), "a shell block was not fenced"
|
||||
|
||||
|
||||
def test_prettify_fallback_fences_instead_of_indenting():
|
||||
"""The catch-all fallback must emit a fenced block, not bare indentation.
|
||||
|
||||
MDX (used for any page that also carries an <Aside> or <FileTree>) has
|
||||
no indented-code-block syntax: a plain 4-space-indented block silently
|
||||
renders as flowed paragraph text there, collapsing every line break. A
|
||||
fenced block is the only fallback shape that's safe in both MDX and
|
||||
plain Markdown.
|
||||
"""
|
||||
import build_manual
|
||||
|
||||
para = ["✘ 1 Fri Jun 12 00:51:21 2026 ← failed"]
|
||||
out = build_manual._render_para(para, None, False)
|
||||
assert out == "```text\n" + para[0] + "\n```", f"unexpected fallback output:\n{out}"
|
||||
|
||||
|
||||
def test_prettify_titles_paths_and_commented_examples():
|
||||
"""A bare file path or a leading '# in x.fish' comment become a title."""
|
||||
import build_manual
|
||||
@@ -657,6 +681,43 @@ def test_prettify_titles_paths_and_commented_examples():
|
||||
)
|
||||
|
||||
|
||||
def test_prettify_titles_label_comments_but_not_explanations():
|
||||
"""A short leading comment titles a shell block; a sentence stays a comment."""
|
||||
import build_manual
|
||||
|
||||
distro = "\n".join([" # Arch / AUR", " pacman -S fish"])
|
||||
out = build_manual.prettify(distro)
|
||||
assert '```fish title="Arch / AUR"\npacman -S fish\n```' in out, (
|
||||
f"a distro label comment was not promoted to the fence title:\n{out}"
|
||||
)
|
||||
|
||||
explanation = "\n".join(
|
||||
[" # Turn it off:", " set -U __fish_config_op_logging off"]
|
||||
)
|
||||
out = build_manual.prettify(explanation)
|
||||
assert 'title=' not in out and "# Turn it off:" in out, (
|
||||
f"a sentence-shaped comment was wrongly promoted to a title:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
def test_prettify_highlights_nested_shell_and_custom_commands():
|
||||
"""A for-loop's indented body and a repo-only command still get shell highlighting."""
|
||||
import build_manual
|
||||
|
||||
loop = "\n".join(
|
||||
[" for v in (set -Un | string match 'x*')", " set -Ue $v", " end"]
|
||||
)
|
||||
out = build_manual.prettify(loop)
|
||||
assert out.startswith("```fish\n") and "```text" not in out, (
|
||||
f"a nested for-loop body lost shell highlighting:\n{out}"
|
||||
)
|
||||
|
||||
out = build_manual.prettify(" fish-deps sync")
|
||||
assert out == '```fish\nfish-deps sync\n```', (
|
||||
f"a repo function name was not recognised as a shell command:\n{out}"
|
||||
)
|
||||
|
||||
|
||||
def test_as_aside_converts_a_single_line_label():
|
||||
"""A `LABEL: text` line becomes a titled <Aside> with the label's type."""
|
||||
import build_manual
|
||||
@@ -872,7 +933,10 @@ def test_customization_notes_render_as_aside():
|
||||
assert aside.count(" - ") == 4, f"expected exactly 4 bullets inside the aside:\n{aside}"
|
||||
assert "- Command shadows (rm, cat, ls, ...) react immediately" in aside
|
||||
assert "- With aliases disabled, rm falls back to bare `command rm`" in aside
|
||||
assert "- Disabled integration commands (spwin, tab, split, hist, logs, upgrade)" in aside
|
||||
assert (
|
||||
"- Disabled integration commands "
|
||||
"(`spwin`, `tab`, `split`, `hist`, `logs`, `upgrade`)" in aside
|
||||
)
|
||||
assert "- On CachyOS, the distro fish config's own aliases" in aside
|
||||
|
||||
|
||||
@@ -1212,6 +1276,243 @@ def test_committed_registry_matches_headers():
|
||||
)
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# codespans: inline code spans added at site-render time
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_REPO = Path(__file__).parent.parent
|
||||
|
||||
|
||||
def _spans(text: str) -> str:
|
||||
return codespans.add_code_spans(text, codespans.vocabulary(_REPO))
|
||||
|
||||
|
||||
def test_codespans_wraps_each_half_of_a_flag_pair():
|
||||
"""`-a/--all` is the manual's usual way of naming a flag and its alias."""
|
||||
got = _spans("Use -a/--all to include both, or -s/--stdout to print.")
|
||||
assert got == "Use `-a`/`--all` to include both, or `-s`/`--stdout` to print.", got
|
||||
|
||||
|
||||
def test_codespans_wraps_override_variables_and_snake_case():
|
||||
got = _spans("Disabled via __fish_config_op_aliases; see _fdc_bins and fish_greeting.")
|
||||
assert got == (
|
||||
"Disabled via `__fish_config_op_aliases`; see `_fdc_bins` and `fish_greeting`."
|
||||
), got
|
||||
|
||||
|
||||
def test_codespans_wraps_paths_vars_env_and_key_chords():
|
||||
cases = {
|
||||
"Sourced from ~/.config/fish/config.fish.": (
|
||||
"Sourced from `~/.config/fish/config.fish`."
|
||||
),
|
||||
"honoring $XDG_CONFIG_HOME/aichat/roles/cli.md.": (
|
||||
"honoring `$XDG_CONFIG_HOME/aichat/roles/cli.md`."
|
||||
),
|
||||
"Launches with NO_TMUX=1 set.": "Launches with `NO_TMUX=1` set.",
|
||||
"end the session with Ctrl-D or Ctrl+Alt+F.": (
|
||||
"end the session with `Ctrl-D` or `Ctrl+Alt+F`."
|
||||
),
|
||||
}
|
||||
for source, want in cases.items():
|
||||
assert _spans(source) == want, f"{source!r} -> {_spans(source)!r}"
|
||||
|
||||
|
||||
def test_codespans_leaves_existing_spans_and_fences_alone():
|
||||
body = "\n".join(
|
||||
[
|
||||
"Already `--wrapped` here.",
|
||||
"",
|
||||
"```fish",
|
||||
"rm -e --empty ~/.config/fish",
|
||||
"```",
|
||||
"",
|
||||
"## --not-a-flag-heading",
|
||||
"",
|
||||
"<Aside type=\"note\" title=\"Note\">",
|
||||
"See --verbose.",
|
||||
"</Aside>",
|
||||
]
|
||||
)
|
||||
got = _spans(body).split("\n")
|
||||
assert got[0] == "Already `--wrapped` here.", got[0]
|
||||
assert got[3] == "rm -e --empty ~/.config/fish", "a fenced line was rewritten"
|
||||
assert got[6] == "## --not-a-flag-heading", "a heading was rewritten"
|
||||
assert got[8].startswith("<Aside"), "component markup was rewritten"
|
||||
assert got[9] == "See `--verbose`.", "aside body was not processed"
|
||||
|
||||
|
||||
def test_codespans_leaves_file_tree_bodies_alone():
|
||||
"""<FileTree> list items are filenames the component renders itself."""
|
||||
body = "<FileTree>\n- ~/.config/fish/\n - config.fish\n</FileTree>"
|
||||
assert _spans(body) == body
|
||||
|
||||
|
||||
def test_codespans_leaves_links_and_urls_alone():
|
||||
cases = [
|
||||
"See [`fish-deps`](/reference/dependency-management/fish-deps/) for more.",
|
||||
"Clone from ssh://git@host/owner/repo.git today.",
|
||||
"Docs live at https://fish.rootiest.fyi/07-customization/ online.",
|
||||
]
|
||||
for source in cases:
|
||||
assert _spans(source) == source, f"{source!r} -> {_spans(source)!r}"
|
||||
|
||||
|
||||
def test_codespans_ignores_prose_that_only_looks_like_code():
|
||||
"""Every one of these has bitten a looser version of the pattern set."""
|
||||
cases = [
|
||||
"the registry -- not the cache -- is rebuilt",
|
||||
"the everyday filesystem-inspection and -modification shadows",
|
||||
"expand !^, !*, !-N and !?str? abbreviations",
|
||||
"grep/cp/mv/wget flag injection",
|
||||
"resolved and/or rejected",
|
||||
"a _really_ important caveat",
|
||||
"the TCP and AGPL acronyms",
|
||||
]
|
||||
for source in cases:
|
||||
assert _spans(source) == source, f"{source!r} -> {_spans(source)!r}"
|
||||
|
||||
|
||||
def test_codespans_wraps_a_command_shadow_chain():
|
||||
got = _spans("Falls back through ov -> bat -> man -> less -> cat.")
|
||||
assert got == "Falls back through `ov` -> `bat` -> `man` -> `less` -> `cat`.", got
|
||||
|
||||
|
||||
def test_codespans_wraps_long_runs_of_tool_names_only():
|
||||
"""Two names, one of them an English word, is a sentence -- not a list."""
|
||||
got = _spans("Supports paru, yay, pacman, apt, dnf, zypper, yum, brew, and pkg.")
|
||||
assert got == (
|
||||
"Supports `paru`, `yay`, `pacman`, `apt`, `dnf`, `zypper`, `yum`, "
|
||||
"`brew`, and `pkg`."
|
||||
), got
|
||||
got = _spans("the custom rm function, trashy, or trash-cli")
|
||||
assert got == "the custom rm function, `trashy`, or trash-cli", got
|
||||
|
||||
|
||||
def test_codespans_wraps_whole_cells_only_in_command_columns():
|
||||
"""A column of expansions becomes code; a column of prose stays prose."""
|
||||
commands = "\n".join(
|
||||
[
|
||||
"| Abbreviation | Description |",
|
||||
"|---|---|",
|
||||
"| `..` | cd .. |",
|
||||
"| `jctl` | journalctl -p 3 -xb |",
|
||||
"| `sudu` | sudo -s |",
|
||||
"| `kt` | kitty (Kitty only) |",
|
||||
]
|
||||
).split("\n")
|
||||
got = _spans("\n".join(commands)).split("\n")
|
||||
assert got[2] == "| `..` | `cd ..` |", got[2]
|
||||
assert got[3] == "| `jctl` | `journalctl -p 3 -xb` |", got[3]
|
||||
assert got[4] == "| `sudu` | `sudo -s` |", got[4]
|
||||
assert got[5] == "| `kt` | `kitty` (Kitty only) |", got[5]
|
||||
|
||||
prose = "\n".join(
|
||||
[
|
||||
"| Command | Active behavior |",
|
||||
"|---|---|",
|
||||
"| `cd` | zoxide frecency-based navigation |",
|
||||
"| `top` | btop resource monitor |",
|
||||
"| `mkdir` | verbose path-tree display on creation |",
|
||||
"| `history` | timestamps prepended to every entry |",
|
||||
]
|
||||
)
|
||||
got = _spans(prose).split("\n")
|
||||
assert got[2] == "| `cd` | `zoxide` frecency-based navigation |", got[2]
|
||||
assert got[3] == "| `top` | `btop` resource monitor |", got[3]
|
||||
|
||||
|
||||
def test_codespans_merges_abutting_new_spans():
|
||||
"""One command line reads as one span, not as a row of adjacent ones."""
|
||||
got = _spans("| `ls` | eza -l -a --icons --hyperlink | system ls |")
|
||||
assert got == "| `ls` | `eza -l -a --icons --hyperlink` | system ls |", got
|
||||
|
||||
|
||||
def test_codespans_vocabulary_comes_from_the_deps_catalog():
|
||||
"""A tool added to _fish_deps_catalog.fish needs no second list."""
|
||||
names = codespans.dependency_names(_REPO)
|
||||
assert {"fzf", "zoxide", "prettyping"} <= names, sorted(names)[:20]
|
||||
vocab = codespans.vocabulary(_REPO)
|
||||
assert "zoxide" in vocab.strict, "an unambiguous tool name should be wrappable"
|
||||
assert "find" not in vocab.strict, "an English word must not be wrappable on sight"
|
||||
assert "find" in vocab.full, "…but it still counts as a command-line opener"
|
||||
|
||||
|
||||
def test_codespans_leaves_indented_code_blocks_alone():
|
||||
"""A four-space block is code, whatever it happens to contain.
|
||||
|
||||
The concat keeps the indented form pandoc wants, so unlike the site
|
||||
this pass meets real indented blocks -- the table of contents among
|
||||
them, which is nothing but a list of command names.
|
||||
"""
|
||||
body = "\n".join(
|
||||
[
|
||||
"Pick a viewer:",
|
||||
"",
|
||||
" 1. ov + bat section navigation",
|
||||
" 2. less plain text with --jump",
|
||||
"",
|
||||
"Then run config-help.",
|
||||
]
|
||||
)
|
||||
got = _spans(body).split("\n")
|
||||
assert got[2] == " 1. ov + bat section navigation", got[2]
|
||||
assert got[3] == " 2. less plain text with --jump", got[3]
|
||||
assert got[5] == "Then run `config-help`.", "prose after the block was skipped"
|
||||
|
||||
|
||||
def test_codespans_reach_the_man_page_pipeline():
|
||||
"""Prose is marked identically wherever it is rendered.
|
||||
|
||||
`build_concat` runs the same pass `build_site` does, so a token the
|
||||
site typesets as code is typeset as code in the man page and
|
||||
`config-help` too, instead of only where the SSOT hand-wrote a
|
||||
backtick.
|
||||
"""
|
||||
import build_manual
|
||||
|
||||
manual = Path(__file__).parent / "manual"
|
||||
text = build_manual.build_concat(manual)
|
||||
assert "`local.fish`" in text, "prose code spans never reached the concat"
|
||||
assert "`tmux`" in text, "a vocabulary command was not wrapped in the concat"
|
||||
|
||||
|
||||
def test_concat_code_spans_never_straddle_a_line():
|
||||
"""`config-help` pairs backticks one line at a time.
|
||||
|
||||
Its `string replace` filters run per line, so a span split across a
|
||||
line break -- ``run `fish-deps\\nupdate` `` -- leaves an unpaired
|
||||
backtick the pager then shows literally. Markdown is happy to wrap
|
||||
one, so nothing else catches this.
|
||||
"""
|
||||
import build_manual
|
||||
|
||||
text = build_manual.build_concat(Path(__file__).parent / "manual")
|
||||
odd = [
|
||||
(n, line)
|
||||
for n, line in enumerate(text.split("\n"), 1)
|
||||
if line.count("`") % 2
|
||||
]
|
||||
assert not odd, f"unpaired backtick, span wraps a line: {odd[:3]}"
|
||||
|
||||
|
||||
def test_concat_section_five_stays_verbatim():
|
||||
"""Section 5's entries are indented blocks, not prose.
|
||||
|
||||
They are generated from the `functions/*.fish` headers and pandoc sets
|
||||
them verbatim, so a backtick there would be a literal character on the
|
||||
page rather than markup.
|
||||
"""
|
||||
import build_manual
|
||||
|
||||
text = build_manual.build_concat(Path(__file__).parent / "manual")
|
||||
body = text.split("\n# 5. ", 1)[1].split("\n# 6. ", 1)[0]
|
||||
offenders = [
|
||||
line for line in body.split("\n") if line.startswith(" ") and "`" in line
|
||||
]
|
||||
assert not offenders, f"backticks inside verbatim entries: {offenders[:3]}"
|
||||
|
||||
|
||||
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
||||
|
||||
|
||||
|
||||
@@ -25,19 +25,19 @@ function __auto_source_fallback_venv --on-variable PWD
|
||||
if set -q DIRENV_DIR; or test -e ".envrc"
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
# 2. If we are already in a venv, check if we've left its tree
|
||||
if set -q VIRTUAL_ENV
|
||||
# Check if the current PWD is still within the directory that owns the venv
|
||||
# (Assuming the venv is at the root of the project)
|
||||
set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV)
|
||||
if not string match -q "$venv_root*" "$PWD"
|
||||
type -q deactivate; and deactivate
|
||||
end
|
||||
return
|
||||
if set -q VIRTUAL_ENV
|
||||
# Check if the current PWD is still within the directory that owns the venv
|
||||
# (Assuming the venv is at the root of the project)
|
||||
set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV)
|
||||
if not string match -q "$venv_root*" "$PWD"
|
||||
type -q deactivate; and deactivate
|
||||
end
|
||||
|
||||
# 3. Only source the venv if we aren't already in one
|
||||
return
|
||||
end
|
||||
|
||||
# 3. Only source the venv if we aren't already in one
|
||||
if test -e ".venv/bin/activate.fish"
|
||||
source .venv/bin/activate.fish
|
||||
end
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
# __config_settings_apply __fish_config_op_greeting session DEFAULT
|
||||
function __config_settings_apply
|
||||
set -l varname $argv[1]
|
||||
set -l scope $argv[2]
|
||||
set -l value $argv[3]
|
||||
set -l scope $argv[2]
|
||||
set -l value $argv[3]
|
||||
|
||||
# stderr is suppressed because setting a value in one scope while the
|
||||
# other scope already holds the same variable makes interactive fish
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __config_settings_draw <cur_row> <cur_scope> <var1> ... <var7>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Renders the 16-line config-settings TUI panel to stdout. Panel width and
|
||||
# horizontal position are chosen automatically from $COLUMNS each call,
|
||||
# so a terminal resize takes effect on the next keypress without any
|
||||
# extra bookkeeping. Four width tiers with a 6-col buffer per side:
|
||||
#
|
||||
# COLUMNS ≥ 90 → 78-wide (IW=76, desc=43 chars)
|
||||
# COLUMNS ≥ 86 → 74-wide (IW=72, desc=39 chars)
|
||||
# COLUMNS ≥ 82 → 70-wide (IW=68, desc=35 chars)
|
||||
# COLUMNS < 82 → 52-wide (IW=50, desc=17 chars) ← default
|
||||
#
|
||||
# The box is horizontally centered via a left-padding prefix on every
|
||||
# output line. \e[16A\e[J erases by line count so the horizontal offset
|
||||
# does not interfere with the redraw loop.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# cur_row 0–6, the currently highlighted row
|
||||
# cur_scope "universal" or "session"
|
||||
# var1–var7 Variable names for rows 0–6 (6 categories + master)
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# __config_settings_draw 0 universal \
|
||||
# __fish_config_op_aliases __fish_config_op_autoexec \
|
||||
# __fish_config_op_overrides __fish_config_op_integrations \
|
||||
# __fish_config_op_logging __fish_config_op_greeting \
|
||||
# __fish_config_opinionated
|
||||
function __config_settings_draw
|
||||
set -l cur_row $argv[1]
|
||||
set -l cur_scope $argv[2]
|
||||
set -l vars $argv[3..]
|
||||
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_sel (set_color --bold magenta)
|
||||
set -l c_hi (set_color --bold white)
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_reset (set_color normal)
|
||||
|
||||
set -l labels Aliases Auto-exec Overrides Integrations Logging Greeting Master
|
||||
|
||||
# ── Width tier: 6-col buffer per side before stepping up ──────────────
|
||||
# IW = inner width (chars between │ │); desc field = IW - 33.
|
||||
# All four layouts are exactly 16 lines tall — panel_h in caller stays 16.
|
||||
set -l iw 50
|
||||
set -l descs \
|
||||
"cmd shadows" \
|
||||
startup \
|
||||
"keys/env/prompt" \
|
||||
"terminal coupling" \
|
||||
scrollback \
|
||||
fish_greeting \
|
||||
"disable all"
|
||||
|
||||
if test "$COLUMNS" -ge 90
|
||||
set iw 76
|
||||
set descs \
|
||||
"shadows: ls→eza, cat→bat, cd→z, rm→trash" \
|
||||
"Fisher bootstrap, themes, py-venv activate" \
|
||||
"vi-mode, bang-bang, PAGER, CDPATH, starship" \
|
||||
"Kitty/WezTerm tab/split fns, notifications" \
|
||||
"scrollback capture & paru/yay AUR wrappers" \
|
||||
"fish_greeting & first-run welcome banner" \
|
||||
"master off-switch: overrides all categories"
|
||||
else if test "$COLUMNS" -ge 86
|
||||
set iw 72
|
||||
set descs \
|
||||
"ls→eza, cat→bat, cd→zoxide, rm→trash" \
|
||||
"Fisher bootstrap, themes, py-venv auto" \
|
||||
"vi-mode, bang-bang, PAGER, starship" \
|
||||
"Kitty/WezTerm fns, done notifications" \
|
||||
"scrollback capture & paru/yay wrappers" \
|
||||
"fish_greeting: first-run welcome banner" \
|
||||
"master off-switch for all categories"
|
||||
else if test "$COLUMNS" -ge 82
|
||||
set iw 68
|
||||
set descs \
|
||||
"ls→eza, cat→bat, cd→z, rm→trash" \
|
||||
"Fisher, themes, py-venv activate" \
|
||||
"vi-mode, bang-bang, PAGER, starship" \
|
||||
"Kitty/WezTerm, done notifications" \
|
||||
"scrollback & paru/yay log wrappers" \
|
||||
"fish_greeting & first-run banner" \
|
||||
"master disable for all categories"
|
||||
end
|
||||
|
||||
set -l HBR (string repeat -n $iw '─')
|
||||
|
||||
# ── Center padding ────────────────────────────────────────────────────
|
||||
# ponytail: floor division — left margin may be 1 col less than right if gap is odd
|
||||
set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
|
||||
|
||||
# ── Top border ────────────────────────────────────────────────────────
|
||||
# ┌─ Opinionated Settings (iw-23)×─ ┐ total = iw+2
|
||||
printf '%s┌─%s Opinionated Settings %s┐\n' \
|
||||
$p $c_head $c_reset(string repeat -n (math $iw - 23) '─')
|
||||
|
||||
# ── Page-tab header ───────────────────────────────────────────────────
|
||||
set -l active_idx 0
|
||||
if test $cur_scope = session
|
||||
set active_idx 1
|
||||
end
|
||||
printf '%s│%s│\n' $p (__config_settings_pagetab $active_idx $iw)
|
||||
|
||||
# ── Top divider ───────────────────────────────────────────────────────
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
|
||||
# ── Category rows 0–5 ─────────────────────────────────────────────────
|
||||
for i in (seq 0 5)
|
||||
set -l idx (math $i + 1)
|
||||
set -l var $vars[$idx]
|
||||
set -l label $labels[$idx]
|
||||
set -l desc $descs[$idx]
|
||||
|
||||
set -l val (__config_settings_get_val $var $cur_scope)
|
||||
|
||||
# Badge: 7 visible chars, coloured
|
||||
set -l badge
|
||||
switch $val
|
||||
case on
|
||||
set badge "$c_ok"" ON$c_reset"
|
||||
case off
|
||||
set badge "$c_err""OFF $c_reset"
|
||||
case '*'
|
||||
set badge "$c_dim""DEFAULT$c_reset"
|
||||
end
|
||||
|
||||
# Cursor: 2 visible chars
|
||||
set -l curs " "
|
||||
if test $i -eq $cur_row
|
||||
set curs "$c_sel▶$c_reset "
|
||||
end
|
||||
|
||||
# Label padded to 12, desc padded to (iw-33), right margin 3
|
||||
set -l lpad (string pad -r -w 12 -- $label)
|
||||
set -l dpad (string pad -r -w (math $iw - 33) -- $desc)
|
||||
|
||||
printf '%s│ %s%s [ %s ] %s │\n' $p $curs $lpad $badge $dpad
|
||||
end
|
||||
|
||||
# ── Separator before Master ───────────────────────────────────────────
|
||||
printf '%s│ %s │\n' $p (string repeat -n (math $iw - 6) '─')
|
||||
|
||||
# ── Master row (index 6) ──────────────────────────────────────────────
|
||||
set -l val (__config_settings_get_val $vars[7] $cur_scope)
|
||||
set -l badge
|
||||
switch $val
|
||||
case on
|
||||
set badge "$c_ok"" ON$c_reset"
|
||||
case off
|
||||
set badge "$c_err""OFF $c_reset"
|
||||
case '*'
|
||||
set badge "$c_dim""DEFAULT$c_reset"
|
||||
end
|
||||
set -l curs " "
|
||||
if test $cur_row -eq 6
|
||||
set curs "$c_sel▶$c_reset "
|
||||
end
|
||||
printf '%s│ %s%s [ %s ] %s │\n' \
|
||||
$p $curs \
|
||||
(string pad -r -w 12 -- Master) \
|
||||
$badge \
|
||||
(string pad -r -w (math $iw - 33) -- $descs[7])
|
||||
|
||||
# ── Filler (Dots Path moved to the Paths page) ────────────────────────
|
||||
printf '%s│ %s%s│\n' $p \
|
||||
"$c_dim→ Tab for Sponge & Path settings$c_reset" \
|
||||
(string repeat -n (math $iw - 34) ' ')
|
||||
printf '%s│%s│\n' $p (string repeat -n $iw ' ')
|
||||
|
||||
# ── Bottom divider ────────────────────────────────────────────────────
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
|
||||
# ── Keybind hint ──────────────────────────────────────────────────────
|
||||
# string pad is width-aware (arrows count as 1 column)
|
||||
set -l hint " ↑↓/kj move ←→/hl set Enter sub-cats Tab pg q quit"
|
||||
printf '%s│%s%s%s│\n' $p $c_dim (string pad -r -w $iw -- $hint) $c_reset
|
||||
|
||||
# ── Bottom border ─────────────────────────────────────────────────────
|
||||
printf '%s└%s┘\n' $p $HBR
|
||||
end
|
||||
@@ -1,151 +0,0 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __config_settings_draw_subcat <cur_row> <cur_scope> <category_var>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Renders the sub-category drill-down page for one C1-C6 category:
|
||||
# the category's own toggle at the top (still meaningful as the cascade
|
||||
# default for its sub-categories), then one row per sub-category from
|
||||
# __config_settings_subcats, sized dynamically instead of the fixed
|
||||
# 6-row layout __config_settings_draw uses for the category list.
|
||||
# Follows the same width-tier and center-padding conventions as
|
||||
# __config_settings_draw so the panel doesn't visibly jump between the
|
||||
# two pages.
|
||||
#
|
||||
# Label and description fields are defensively truncated to their field
|
||||
# width before padding (string pad only ever grows a string, never
|
||||
# shrinks it) -- sub-category labels/descriptions are static data from
|
||||
# __config_settings_subcats, not authored per width-tier the way
|
||||
# __config_settings_draw's own category descriptions are, so a couple of
|
||||
# them are longer than the narrower tiers' fields (e.g. "Notifications"
|
||||
# is 13 chars against a 12-char label field; several descriptions run
|
||||
# well past the 17-char field at the narrowest tier). Truncating keeps
|
||||
# the box perfectly rectangular in every case instead of only in the
|
||||
# cases the static text happens to fit.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# cur_row 0-based highlighted row (0 = the category's own toggle;
|
||||
# 1..N = sub-category rows)
|
||||
# cur_scope "universal" or "session"
|
||||
# category_var One of the six __fish_config_op_<category> names
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# __config_settings_draw_subcat 1 universal __fish_config_op_aliases
|
||||
function __config_settings_draw_subcat
|
||||
set -l cur_row $argv[1]
|
||||
set -l cur_scope $argv[2]
|
||||
set -l category_var $argv[3]
|
||||
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_sel (set_color --bold magenta)
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_reset (set_color normal)
|
||||
|
||||
set -l rows (__config_settings_subcats $category_var)
|
||||
set -l n (count $rows)
|
||||
|
||||
# ── Width tier: matches __config_settings_draw's 6-col-per-side steps ──
|
||||
set -l iw 50
|
||||
if test "$COLUMNS" -ge 90
|
||||
set iw 76
|
||||
else if test "$COLUMNS" -ge 86
|
||||
set iw 72
|
||||
else if test "$COLUMNS" -ge 82
|
||||
set iw 68
|
||||
end
|
||||
set -l HBR (string repeat -n $iw '─')
|
||||
set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
|
||||
|
||||
# Label field is 13 wide (one wider than __config_settings_draw's 12) --
|
||||
# the longest real sub-category label ("Notifications") is 13 chars.
|
||||
# Description field absorbs the difference so every row still totals
|
||||
# iw+2, matching the surrounding box lines exactly.
|
||||
set -l label_w 13
|
||||
set -l desc_w (math $iw - 34)
|
||||
|
||||
set -l cat_label (string replace -r '^__fish_config_op_' '' -- $category_var)
|
||||
# Scope indicator: toggling a row on this page writes -U (Universal,
|
||||
# persistent) or -g (Session, this-shell-only) -- the title must say
|
||||
# which, since it isn't otherwise visible anywhere on the page.
|
||||
set -l scope_label Universal
|
||||
test "$cur_scope" = session; and set scope_label Session
|
||||
# Title layout is "┌─ Sub-categories: <label> (<scope>) ───┐"; the
|
||||
# dash count must absorb every visible char added around cat_label so
|
||||
# the line still totals iw+2, matching the surrounding box exactly --
|
||||
# see the DESCRIPTION doc comment above for why this is hand-verified,
|
||||
# not eyeballed.
|
||||
set -l title_dashes (math $iw - (string length -- $cat_label) - (string length -- $scope_label) - 22)
|
||||
printf '%s┌─%s Sub-categories: %s (%s)%s %s┐\n' \
|
||||
$p $c_head "$cat_label" $scope_label "$c_reset" (string repeat -n (math "max(0, $title_dashes)") '─')
|
||||
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
|
||||
# Row 0: the category's own toggle, still meaningful as the cascade
|
||||
# default any DEFAULT-valued sub-category below falls back to.
|
||||
set -l cat_val (__config_settings_get_val $category_var $cur_scope)
|
||||
set -l cat_badge
|
||||
switch $cat_val
|
||||
case on
|
||||
set cat_badge "$c_ok"" ON$c_reset"
|
||||
case off
|
||||
set cat_badge "$c_err""OFF $c_reset"
|
||||
case '*'
|
||||
set cat_badge "$c_dim""DEFAULT$c_reset"
|
||||
end
|
||||
set -l cat_curs " "
|
||||
if test $cur_row -eq 0
|
||||
set cat_curs "$c_sel▶$c_reset "
|
||||
end
|
||||
set -l cat_desc "cascade default"
|
||||
if test $iw -ge 68
|
||||
set cat_desc "default for all sub-cats below"
|
||||
end
|
||||
if test $iw -ge 72
|
||||
set cat_desc "default for all sub-categories below"
|
||||
end
|
||||
printf '%s│ %s%s [ %s ] %s │\n' $p $cat_curs \
|
||||
(string pad -r -w $label_w -- "(category)") $cat_badge \
|
||||
(string pad -r -w $desc_w -- (string sub -l $desc_w -- $cat_desc))
|
||||
|
||||
printf '%s│ %s │\n' $p (string repeat -n (math $iw - 6) '─')
|
||||
|
||||
for i in (seq 1 $n)
|
||||
set -l fields (string split -- \t $rows[$i])
|
||||
set -l slug $fields[1]
|
||||
set -l label $fields[2]
|
||||
set -l desc $fields[3]
|
||||
set -l subcat_var "$category_var"_(string replace -a -- '-' '_' $slug)
|
||||
|
||||
set -l val (__config_settings_get_val $subcat_var $cur_scope)
|
||||
set -l badge
|
||||
switch $val
|
||||
case on
|
||||
set badge "$c_ok"" ON$c_reset"
|
||||
case off
|
||||
set badge "$c_err""OFF $c_reset"
|
||||
case '*'
|
||||
set badge "$c_dim""DEFAULT$c_reset"
|
||||
end
|
||||
|
||||
set -l curs " "
|
||||
if test $i -eq $cur_row
|
||||
set curs "$c_sel▶$c_reset "
|
||||
end
|
||||
|
||||
set -l lpad (string pad -r -w $label_w -- (string sub -l $label_w -- $label))
|
||||
set -l dpad (string pad -r -w $desc_w -- (string sub -l $desc_w -- $desc))
|
||||
printf '%s│ %s%s [ %s ] %s │\n' $p $curs $lpad $badge $dpad
|
||||
end
|
||||
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
set -l hint " ↑↓/kj move ←→/hl set Esc back q quit"
|
||||
printf '%s│%s%s%s│\n' $p $c_dim (string pad -r -w $iw -- $hint) $c_reset
|
||||
printf '%s└%s┘\n' $p $HBR
|
||||
end
|
||||
@@ -1,184 +0,0 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __config_settings_draw_value <cur_row> <page>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Renders a config-settings value page (Sponge or Paths) as exactly 16
|
||||
# lines, matching the box geometry of the opinionated toggle page so the
|
||||
# caller's wrap-aware erase (panel_h=16) is unchanged. Each row shows a
|
||||
# label, a badge, and the variable's current value (or its default hint).
|
||||
# Toggle-type rows (the two sponge booleans) reuse the ON/OFF/DEFAULT badge;
|
||||
# value rows (path/int/list/string) show a type badge and the live value.
|
||||
#
|
||||
# Page width follows the same $COLUMNS tiers as the toggle page for a
|
||||
# consistent look; exact width is not required for the erase (the erase
|
||||
# over-clears to end of screen using the 78-col worst case).
|
||||
#
|
||||
# ARGUMENTS
|
||||
# cur_row 0-based highlighted row within the page
|
||||
# page "sponge" or "paths"
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# EXAMPLE
|
||||
# __config_settings_draw_value 0 sponge
|
||||
function __config_settings_draw_value
|
||||
set -l cur_row $argv[1]
|
||||
set -l page $argv[2]
|
||||
# Inline-edit state: when argv[3] is "edit", the cur_row field renders the
|
||||
# live input buffer (argv[4]) with a caret instead of its stored value.
|
||||
set -l edit_mode $argv[3]
|
||||
set -l edit_buf $argv[4]
|
||||
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_sel (set_color --bold magenta)
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_reset (set_color normal)
|
||||
|
||||
# ── Page row metadata (parallel lists) ────────────────────────────────
|
||||
set -l title
|
||||
set -l vars
|
||||
set -l labels
|
||||
set -l types
|
||||
set -l hints # default hint shown when unset
|
||||
set -l active_idx
|
||||
if test $page = sponge
|
||||
set title "Sponge Settings"
|
||||
set active_idx 2
|
||||
set vars sponge_delay sponge_purge_only_on_exit sponge_allow_previously_successful sponge_successful_exit_codes __fish_sponge_extra_sensitive
|
||||
set labels Delay "Purge@exit" "Allow prev" "OK codes" "Extra secret"
|
||||
set types int bool bool list list
|
||||
set hints 2 false true 0 "(none)"
|
||||
else
|
||||
set title "Path Settings"
|
||||
set active_idx 3
|
||||
set vars __fish_scrollback_history_dir __fish_scrollback_history_max_files __fish_user_dots_path __fish_user_dots_symlink
|
||||
set labels "Log dir" "Log max" "Dots path" "Dots link"
|
||||
set types path int path bool
|
||||
set hints "~/.terminal_history" 100 "(default)" on
|
||||
end
|
||||
set -l nrows (count $vars)
|
||||
|
||||
# ── Width tier (same thresholds as the toggle page) ───────────────────
|
||||
set -l iw 50
|
||||
if test "$COLUMNS" -ge 90
|
||||
set iw 76
|
||||
else if test "$COLUMNS" -ge 86
|
||||
set iw 72
|
||||
else if test "$COLUMNS" -ge 82
|
||||
set iw 68
|
||||
end
|
||||
set -l HBR (string repeat -n $iw '─')
|
||||
set -l p (string repeat -n (math --scale=0 "max(0, ($COLUMNS - ($iw + 2)) / 2)") ' ')
|
||||
|
||||
# ── Line 1: top border with title ─────────────────────────────────────
|
||||
set -l title_dashes (math $iw - (string length -- $title) - 3)
|
||||
printf '%s┌─%s %s %s┐\n' \
|
||||
$p $c_head "$title$c_reset" (string repeat -n $title_dashes '─')
|
||||
|
||||
# ── Line 2: page-tab header ───────────────────────────────────────────
|
||||
printf '%s│%s│\n' $p (__config_settings_pagetab $active_idx $iw)
|
||||
|
||||
# ── Line 3: divider ───────────────────────────────────────────────────
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
|
||||
# ── Value rows ────────────────────────────────────────────────────────
|
||||
for i in (seq 0 (math $nrows - 1))
|
||||
set -l idx (math $i + 1)
|
||||
set -l var $vars[$idx]
|
||||
set -l label $labels[$idx]
|
||||
set -l type $types[$idx]
|
||||
set -l hint $hints[$idx]
|
||||
|
||||
# Badge (7 visible cols) + value field
|
||||
set -l badge
|
||||
set -l field
|
||||
if test $type = bool
|
||||
# Booleans store true/false (sponge convention); unset = DEFAULT.
|
||||
set -l val (__config_settings_get_raw $var)
|
||||
switch $val
|
||||
case true
|
||||
set badge "$c_ok"" ON$c_reset"
|
||||
case false
|
||||
set badge "$c_err""OFF $c_reset"
|
||||
case '*'
|
||||
set badge "$c_dim""DEFAULT$c_reset"
|
||||
end
|
||||
set field "default: $hint"
|
||||
else
|
||||
set -l raw (__config_settings_get_raw $var)
|
||||
if test "$raw" = DEFAULT
|
||||
set badge "$c_dim""DEFAULT$c_reset"
|
||||
set field "$hint"
|
||||
else
|
||||
switch $type
|
||||
case path
|
||||
set badge "$c_ok"" PATH $c_reset"
|
||||
case int
|
||||
set badge "$c_ok"" INT $c_reset"
|
||||
case list
|
||||
set badge "$c_ok"" LIST $c_reset"
|
||||
case '*'
|
||||
set badge "$c_ok"" STR $c_reset"
|
||||
end
|
||||
set field "$raw"
|
||||
end
|
||||
end
|
||||
|
||||
# Inline edit: render the active row's field as the live buffer with a
|
||||
# block caret, tail-anchored so the caret stays visible as text grows.
|
||||
if test "$edit_mode" = edit -a $i -eq $cur_row
|
||||
set -l fw (math $iw - 33)
|
||||
set -l avail (math $fw - 1)
|
||||
set -l shown "$edit_buf"
|
||||
set -l blen (string length -- "$edit_buf")
|
||||
if test $blen -gt $avail
|
||||
set shown (string sub -s (math $blen - $avail + 1) -- "$edit_buf")
|
||||
end
|
||||
set badge "$c_head"" EDIT $c_reset"
|
||||
set field "$shown"(set_color --reverse)" "(set_color normal)
|
||||
end
|
||||
|
||||
set -l curs " "
|
||||
if test $i -eq $cur_row
|
||||
set curs "$c_sel▶$c_reset "
|
||||
end
|
||||
|
||||
set -l fw (math $iw - 33)
|
||||
set -l lpad (string pad -r -w 12 -- $label)
|
||||
# The edit field is already length-constrained and contains a reverse
|
||||
# caret; running it through `string shorten` miscounts the escapes, so
|
||||
# pad it directly. Non-edit fields still shorten to add an ellipsis.
|
||||
set -l fpad
|
||||
if test "$edit_mode" = edit -a $i -eq $cur_row
|
||||
set fpad (string pad -r -w $fw -- "$field")
|
||||
else
|
||||
set fpad (string pad -r -w $fw -- (string shorten -m $fw -- "$field"))
|
||||
end
|
||||
printf '%s│ %s%s [ %s ] %s │\n' $p $curs $lpad $badge $fpad
|
||||
end
|
||||
|
||||
# ── Pad blank rows so chrome(6) + nrows + blanks = 16 ─────────────────
|
||||
set -l blanks (math 10 - $nrows)
|
||||
for i in (seq 1 $blanks)
|
||||
printf '%s│%s│\n' $p (string repeat -n $iw ' ')
|
||||
end
|
||||
|
||||
# ── Bottom divider ────────────────────────────────────────────────────
|
||||
printf '%s│%s│\n' $p $HBR
|
||||
|
||||
# ── Hint line (changes while editing) ─────────────────────────────────
|
||||
set -l hint_line " ↑↓ move Enter edit ←/h clear Tab page q quit"
|
||||
if test "$edit_mode" = edit
|
||||
set hint_line " type value Enter save Esc cancel ⌫ delete"
|
||||
end
|
||||
printf '%s│%s%s%s│\n' $p $c_dim (string pad -r -w $iw -- $hint_line) $c_reset
|
||||
|
||||
# ── Bottom border ─────────────────────────────────────────────────────
|
||||
printf '%s└%s┘\n' $p $HBR
|
||||
end
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Returns the current value of a named variable in the specified scope by
|
||||
# parsing `set --show` output. Outputs "on", "off", or "DEFAULT" (when
|
||||
# parsing set --show output. Outputs "on", "off", or "DEFAULT" (when
|
||||
# the variable is not set in that scope). Scope "session" maps to "global"
|
||||
# in fish's internal terminology.
|
||||
#
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __config_settings_pagetab <active_idx> <iw>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Renders the four-page tab strip used as the header line of every
|
||||
# config-settings page. Pages: 0 Universal, 1 Session, 2 Sponge, 3 Paths.
|
||||
# The active page is marked with a filled bullet and bold text; the others
|
||||
# with a hollow bullet. The returned string is padded to exactly <iw>
|
||||
# printable columns (the caller adds the │ │ border and center offset).
|
||||
#
|
||||
# ARGUMENTS
|
||||
# active_idx 0–3, the active page index
|
||||
# iw inner width in columns to pad the strip to
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# RETURNS
|
||||
# The rendered tab strip, printed to stdout (no trailing newline beyond printf's)
|
||||
#
|
||||
# EXAMPLE
|
||||
# set strip (__config_settings_pagetab 2 76)
|
||||
function __config_settings_pagetab
|
||||
set -l active $argv[1]
|
||||
set -l iw $argv[2]
|
||||
|
||||
set -l c_hi (set_color --bold white)
|
||||
set -l c_reset (set_color normal)
|
||||
set -l names Universal Session Sponge Paths
|
||||
|
||||
set -l strip ' '
|
||||
for i in (seq 0 3)
|
||||
set -l idx (math $i + 1)
|
||||
if test $i -eq $active
|
||||
set strip "$strip$c_hi●$names[$idx]$c_reset "
|
||||
else
|
||||
set strip "$strip○$names[$idx] "
|
||||
end
|
||||
end
|
||||
# string pad is width-aware: color escapes count as 0 columns, the bullets
|
||||
# and letters as their printable width.
|
||||
string pad -r -w $iw -- $strip
|
||||
end
|
||||
@@ -1,97 +0,0 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __config_settings_read_key
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Reads a single keypress directly from the controlling terminal in raw
|
||||
# mode and echoes a normalized token naming the key. Bypasses fish's
|
||||
# `read` builtin, whose interactive line editor swallows Tab and arrow
|
||||
# keys (and prints a `read> ` prompt) — none of which is usable for a TUI.
|
||||
#
|
||||
# The terminal is put into raw, no-echo mode with a 0.1s inter-byte timer
|
||||
# (`stty raw -echo min 1 time 1`) so a multi-byte escape sequence (e.g.
|
||||
# an arrow key, ESC [ A) is captured in one read while a lone key returns
|
||||
# promptly. Original terminal settings are always restored before return.
|
||||
#
|
||||
# In raw mode Ctrl-C does not raise SIGINT; it arrives as byte 3 (ETX),
|
||||
# which is reported as the token "quit".
|
||||
#
|
||||
# ARGUMENTS
|
||||
# (none)
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 A key was read
|
||||
# 1 The terminal could not be put into raw mode (stdin is not a TTY)
|
||||
#
|
||||
# RETURNS
|
||||
# One token, printed to stdout:
|
||||
# up down left right arrow keys
|
||||
# space tab backtab enter escape backspace
|
||||
# quit Ctrl-C (byte 3) in raw mode
|
||||
# <char> any other single printable character
|
||||
# "" nothing decodable was read
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l key (__config_settings_read_key)
|
||||
# or return # not a TTY — bail
|
||||
# switch $key
|
||||
# case up; echo "moved up"
|
||||
# case space; echo "toggled"
|
||||
# end
|
||||
function __config_settings_read_key
|
||||
# Snapshot current terminal settings; failure means stdin is not a TTY.
|
||||
set -l saved (stty -g </dev/tty 2>/dev/null)
|
||||
or return 1
|
||||
|
||||
# Raw, no-echo. min 0 / time 3: return after 0.3s even with no bytes (poll
|
||||
# interval for resize detection), or immediately when any bytes arrive.
|
||||
# Escape sequences (e.g. arrow keys) arrive fast enough to land in one read.
|
||||
stty raw -echo min 0 time 3 </dev/tty 2>/dev/null
|
||||
|
||||
# One read() of up to 3 bytes — covers ESC [ A style sequences. od emits
|
||||
# the bytes as space-separated decimal codes.
|
||||
set -l codes (dd if=/dev/tty bs=3 count=1 2>/dev/null \
|
||||
| od -An -tu1 2>/dev/null | string trim | string split -n ' ')
|
||||
|
||||
# Restore the terminal before doing anything else.
|
||||
stty $saved </dev/tty 2>/dev/null
|
||||
|
||||
switch (string join ' ' $codes)
|
||||
case '27 91 65'
|
||||
echo up
|
||||
case '27 91 66'
|
||||
echo down
|
||||
case '27 91 67'
|
||||
echo right
|
||||
case '27 91 68'
|
||||
echo left
|
||||
case '27 91 90'
|
||||
echo backtab
|
||||
case 27
|
||||
echo escape
|
||||
case 9
|
||||
echo tab
|
||||
case 32
|
||||
echo space
|
||||
case 10 13
|
||||
echo enter
|
||||
case 8 127
|
||||
echo backspace
|
||||
case 3
|
||||
echo quit
|
||||
case ''
|
||||
echo ''
|
||||
case '*'
|
||||
# Single printable byte → emit its character; ignore stray
|
||||
# multi-byte sequences we do not recognise. The two-step octal
|
||||
# form avoids fish mangling a one-shot '\\%03o' format string.
|
||||
if test (count $codes) -eq 1
|
||||
set -l oct (printf '%03o' $codes[1])
|
||||
printf '%b\n' "\\$oct"
|
||||
else
|
||||
echo ''
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,8 +27,8 @@
|
||||
# __config_settings_set_value __fish_user_dots_path path '' # reset
|
||||
function __config_settings_set_value
|
||||
set -l varname $argv[1]
|
||||
set -l type $argv[2]
|
||||
set -l value $argv[3]
|
||||
set -l type $argv[2]
|
||||
set -l value $argv[3]
|
||||
|
||||
# stderr suppressed: editing a universal while a global of the same name
|
||||
# shadows it makes interactive fish emit a shadow warning that would
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# DEPENDENCIES
|
||||
# __config_settings_get_val, __config_settings_get_raw,
|
||||
# __config_settings_subcats
|
||||
#
|
||||
# SYNOPSIS
|
||||
# __config_settings_state
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Dumps everything scripts/config-settings-tui.py needs to render the
|
||||
# settings TUI: the current value of every opinionated-component,
|
||||
# sponge, scrollback and user-dots variable, plus the sub-category
|
||||
# taxonomy from __config_settings_subcats.
|
||||
#
|
||||
# Two record types, separated by RS (0x1e), fields separated by US
|
||||
# (0x1f). Both are ASCII control characters, so no value these variables
|
||||
# can hold needs escaping on the way through:
|
||||
#
|
||||
# var<US><scope><US><name><US><value>
|
||||
# sub<US><category_var><US><slug><US><label><US><description>
|
||||
#
|
||||
# Only variables that are actually set are emitted; the TUI renders an
|
||||
# absent variable as DEFAULT. That is what keeps the variable list out of
|
||||
# this function -- names are discovered with `set --names` and filtered by
|
||||
# prefix, so a new sub-category needs no edit here, only in
|
||||
# __config_settings_subcats.
|
||||
#
|
||||
# Toggle variables are dumped once per scope (universal and session), since
|
||||
# the TUI edits those scopes independently. Sponge and Paths variables are
|
||||
# universal-only and dumped as the value the running shell resolves,
|
||||
# space-joined for list variables.
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# RETURNS
|
||||
# The RS/US-separated state dump, printed to stdout
|
||||
#
|
||||
# EXAMPLE
|
||||
# __config_settings_state | string split \x1e
|
||||
function __config_settings_state --description 'Dump config-settings state for the curses TUI'
|
||||
# ── Variable values ───────────────────────────────────────────────────
|
||||
for name in (set --names)
|
||||
switch $name
|
||||
case '__fish_config_op_registry_*'
|
||||
# conf.d data table, not a setting -- shares the op_ prefix.
|
||||
continue
|
||||
case '__fish_config_op_*' __fish_config_opinionated
|
||||
# Toggles: both scopes, independently editable.
|
||||
for scope in universal session
|
||||
set -l val (__config_settings_get_val $name $scope)
|
||||
test "$val" = DEFAULT; and continue
|
||||
printf '%s\x1f%s\x1f%s\x1f%s\x1e' var $scope $name $val
|
||||
end
|
||||
case 'sponge_*' __fish_sponge_extra_sensitive \
|
||||
'__fish_scrollback_history_*' __fish_user_dots_path \
|
||||
__fish_user_dots_symlink
|
||||
# Value rows: universal-only, list variables space-joined.
|
||||
set -l val (__config_settings_get_raw $name)
|
||||
test "$val" = DEFAULT; and continue
|
||||
printf '%s\x1f%s\x1f%s\x1f%s\x1e' var universal $name $val
|
||||
end
|
||||
end
|
||||
|
||||
# ── Sub-category taxonomy ─────────────────────────────────────────────
|
||||
for cvar in __fish_config_op_aliases __fish_config_op_autoexec \
|
||||
__fish_config_op_overrides __fish_config_op_integrations \
|
||||
__fish_config_op_logging __fish_config_op_greeting
|
||||
for row in (__config_settings_subcats $cvar)
|
||||
set -l f (string split -- \t $row)
|
||||
printf '%s\x1f%s\x1f%s\x1f%s\x1f%s\x1e' sub $cvar $f[1] $f[2] $f[3]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,9 +28,9 @@ function __config_settings_subcats --description 'List the sub-categories for on
|
||||
case __fish_config_op_aliases
|
||||
printf '%s\t%s\t%s\n' \
|
||||
filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \
|
||||
search Search "rg" \
|
||||
search Search rg \
|
||||
network Network "ping, ssh, yt-dlp" \
|
||||
monitor Monitor "top" \
|
||||
monitor Monitor top \
|
||||
shell-tools Shell-tools "bash, less, help" \
|
||||
dev-tools Dev-tools "claude, edit, agy"
|
||||
case __fish_config_op_autoexec
|
||||
@@ -44,14 +44,15 @@ function __config_settings_subcats --description 'List the sub-categories for on
|
||||
printf '%s\t%s\t%s\n' \
|
||||
key-bindings Key-bindings "vi-mode, autopair, puffer, bang-bang" \
|
||||
environment Environment "PATH, PAGER, EDITOR, CDPATH" \
|
||||
prompt Prompt "Starship, right prompt, theme + FZF colors"
|
||||
prompt Prompt "Starship, right prompt, theme + FZF colors" \
|
||||
privacy Privacy "DO_NOT_TRACK, DISABLE_TELEMETRY"
|
||||
case __fish_config_op_integrations
|
||||
printf '%s\t%s\t%s\n' \
|
||||
terminal-abbrs Term-abbrs "Kitty/WezTerm abbreviations" \
|
||||
window-mgmt Window-mgmt "spwin, tab, split" \
|
||||
notifications Notifications "done, WakaTime hook" \
|
||||
history-logs History-logs "hist, logs" \
|
||||
pkg-upgrade Pkg-upgrade "upgrade"
|
||||
pkg-upgrade Pkg-upgrade upgrade
|
||||
case __fish_config_op_logging
|
||||
printf '%s\t%s\t%s\n' \
|
||||
terminal-capture Term-capture "Kitty watcher, smart_exit scrollback" \
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
# Looks up "<identity>:<site>" (site defaults to the empty/unnamed site)
|
||||
# in the generated component registry. No registry entry (unclassified,
|
||||
# or a doc header with no # COMPONENT section) resolves to enabled --
|
||||
# the same fail-open default as an explicit `always/on` tag, so
|
||||
# the same fail-open default as an explicit always/on tag, so
|
||||
# user-authored and third-party functions that never call this guard in
|
||||
# the first place are unaffected, and one that somehow does is never
|
||||
# silently broken by a missing header. A found `always/off` tag
|
||||
# disables unconditionally; a found `always/on` tag enables
|
||||
# silently broken by a missing header. A found always/off tag
|
||||
# disables unconditionally; a found always/on tag enables
|
||||
# unconditionally, short-circuiting before any other tagged
|
||||
# sub-category is evaluated. Otherwise every tagged sub-category must
|
||||
# pass the cascade (AND semantics).
|
||||
|
||||
@@ -28,84 +28,38 @@ function __fish_config_sync_logging --description 'Sync C5 logging state: sentin
|
||||
set config_home "$HOME/.config"
|
||||
end
|
||||
set -l sentinel "$config_home/fish/.logging_disabled"
|
||||
set -l paru_wrapper "$HOME/.local/bin/paru"
|
||||
set -l yay_wrapper "$HOME/.local/bin/yay"
|
||||
set -l wrapper_version 1
|
||||
|
||||
if __fish_config_op_enabled (status current-function)
|
||||
# Logging enabled: remove sentinel and regenerate wrappers if binaries exist
|
||||
# Logging enabled: remove sentinel
|
||||
rm -f $sentinel
|
||||
|
||||
# Restart tmux pipe-pane for the current pane if inside tmux
|
||||
_tmux_pipe_log
|
||||
|
||||
if test -x /usr/bin/paru
|
||||
mkdir -p (dirname $paru_wrapper)
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
"# paru-wrapper-version: $wrapper_version" \
|
||||
'# Auto-generated by conf.d/paru-wrapper.fish — do not edit by hand.' \
|
||||
'# Tees paru output to a timestamped log file and prunes old ones.' \
|
||||
'set -o pipefail' \
|
||||
'' \
|
||||
'log_dir="${SCROLLBACK_HISTORY_DIR:-$HOME/.terminal_history}"' \
|
||||
'mkdir -p "$log_dir"' \
|
||||
'log_file="$log_dir/paru_$(date +%Y-%m-%d_%H-%M-%S).log"' \
|
||||
'' \
|
||||
'/usr/bin/paru "$@" 2>&1 | tee "$log_file"' \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
>$paru_wrapper
|
||||
chmod +x $paru_wrapper
|
||||
end
|
||||
|
||||
if test -x /usr/bin/yay
|
||||
mkdir -p (dirname $yay_wrapper)
|
||||
printf '%s\n' \
|
||||
'#!/usr/bin/env bash' \
|
||||
"# yay-wrapper-version: $wrapper_version" \
|
||||
'# Auto-generated by conf.d/yay-wrapper.fish — do not edit by hand.' \
|
||||
'# Tees yay output to a timestamped log file and prunes old ones.' \
|
||||
'set -o pipefail' \
|
||||
'' \
|
||||
'log_dir="${SCROLLBACK_HISTORY_DIR:-$HOME/.terminal_history}"' \
|
||||
'mkdir -p "$log_dir"' \
|
||||
'log_file="$log_dir/yay_$(date +%Y-%m-%d_%H-%M-%S).log"' \
|
||||
'' \
|
||||
'/usr/bin/yay "$@" 2>&1 | tee "$log_file"' \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
>$yay_wrapper
|
||||
chmod +x $yay_wrapper
|
||||
end
|
||||
else
|
||||
# Logging disabled: create sentinel and remove any generated wrappers
|
||||
# Logging disabled: create sentinel
|
||||
mkdir -p (dirname $sentinel)
|
||||
touch $sentinel
|
||||
|
||||
if test -f $paru_wrapper
|
||||
and grep -q "# paru-wrapper-version:" $paru_wrapper 2>/dev/null
|
||||
rm -f $paru_wrapper
|
||||
end
|
||||
|
||||
if test -f $yay_wrapper
|
||||
and grep -q "# yay-wrapper-version:" $yay_wrapper 2>/dev/null
|
||||
rm -f $yay_wrapper
|
||||
end
|
||||
|
||||
# Stop tmux pipe-pane for the current pane if inside tmux
|
||||
if set -q TMUX
|
||||
tmux pipe-pane 2>/dev/null
|
||||
end
|
||||
end
|
||||
|
||||
# Delegate paru/yay wrapper (re)generation and removal to the canonical
|
||||
# generators. They resolve the real binary via __fish_real_command
|
||||
# (never /usr/bin-assumed) and independently gate on their own C2/C5
|
||||
# keys, so sourcing them here covers both the enabled-regenerate and
|
||||
# disabled-remove cases without duplicating that logic. Previously this
|
||||
# function carried its own inferior copy (tee instead of a PTY, no
|
||||
# progress-bar rendering, hard-coded /usr/bin/paru|yay), which fought
|
||||
# the canonical generator for the wrapper file on every version-marker
|
||||
# mismatch.
|
||||
#
|
||||
# Routed through _fish_source_scoped: both files `return` early on
|
||||
# several guard checks, and a sourced `return` exits the *calling*
|
||||
# function, which would otherwise abort this function and skip
|
||||
# whichever of paru/yay hadn't run yet.
|
||||
_fish_source_scoped "$__fish_config_dir/conf.d/paru-wrapper.fish"
|
||||
_fish_source_scoped "$__fish_config_dir/conf.d/yay-wrapper.fish"
|
||||
end
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __fish_help_header <name> [args...]
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Prints <name>'s man-page comment header as a help menu on stdout.
|
||||
# Intended as the first statement of a user-facing function's body:
|
||||
#
|
||||
# __fish_help_header (status current-function) $argv; and return 0
|
||||
#
|
||||
# Returns 1 -- printing nothing -- ONLY when args[1] is not a help flag.
|
||||
# Every other outcome, including an unreadable or headerless source
|
||||
# file, prints something and returns 0. That asymmetry is load-bearing:
|
||||
# a return of 1 means "run the real body", and the real body of upgrade
|
||||
# is `paru -Syu --noconfirm`. A parse failure must never return 1.
|
||||
#
|
||||
# Only args[1] is inspected, never the whole list. wake-lock, bkg,
|
||||
# split and spwin take a command to run as their arguments, so
|
||||
# scanning all of $argv would make `wake-lock rsync --help` print
|
||||
# wake-lock's own help instead of running rsync.
|
||||
#
|
||||
# The header is read from the caller's source at call time rather than
|
||||
# from the generated manual, so it cannot go stale between a header
|
||||
# edit and a docs rebuild.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# name The calling function's name, from (status current-function)
|
||||
# args... The caller's $argv, forwarded verbatim
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Help was printed, including the degraded fallback
|
||||
# 1 args[1] is not -h/--help; the caller should carry on
|
||||
#
|
||||
# EXAMPLE
|
||||
# __fish_help_header (status current-function) $argv; and return 0
|
||||
#
|
||||
# NOTES
|
||||
# Section labels are those of the manual SSOT parser in
|
||||
# docs/manualtools.py. CATEGORY, COMPONENT and DEPENDENCIES are build
|
||||
# metadata and are suppressed; SYNOPSIS renders as USAGE and EXAMPLE as
|
||||
# EXAMPLES.
|
||||
function __fish_help_header --argument-names name
|
||||
# First argument only -- see DESCRIPTION.
|
||||
contains -- "$argv[2]" -h --help; or return 1
|
||||
|
||||
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)
|
||||
if not test -f "$file"
|
||||
# Quoted: set_color yields an EMPTY LIST under TERM=dumb, and an
|
||||
# unquoted empty list in a concatenation annihilates the whole
|
||||
# word -- the title line would silently vanish wherever colour is
|
||||
# off, which is exactly where a test would be reading it.
|
||||
echo "$c_ttl$name$c_rst"
|
||||
echo $miss
|
||||
return 0
|
||||
end
|
||||
|
||||
# Collect the contiguous comment run directly above `function <name>`,
|
||||
# walking backwards. This resolves multi-header files (fish-deps, gi,
|
||||
# y) without reimplementing manualtools._block_identity, and is more
|
||||
# accurate at runtime: in dops.fish it finds the header above
|
||||
# `function docker` rather than attributing it to the file stem.
|
||||
# One blank separator line is tolerated -- sponge_filter_secrets.fish
|
||||
# is the only file that has one. Do not remove this tolerance as dead
|
||||
# code.
|
||||
set -l lines (string split \n -- (command cat $file))
|
||||
set -l pat '^\s*function\s+'(string escape --style=regex -- $name)'(\s|$)'
|
||||
set -l start 0
|
||||
for i in (seq (count $lines))
|
||||
if string match -qr -- $pat $lines[$i]
|
||||
set start $i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
set -l header
|
||||
if test $start -gt 1
|
||||
set -l j (math $start - 1)
|
||||
if test -z (string trim -- "$lines[$j]")
|
||||
set j (math $j - 1)
|
||||
end
|
||||
while test $j -ge 1; and string match -q '#*' -- $lines[$j]
|
||||
set -p header $lines[$j]
|
||||
set j (math $j - 1)
|
||||
end
|
||||
end
|
||||
|
||||
# Render. Comment lines before the first `# LABEL` -- the copyright
|
||||
# preamble -- carry no label and are dropped, matching
|
||||
# manualtools._header_blocks.
|
||||
set -l skip CATEGORY COMPONENT DEPENDENCIES
|
||||
set -l label ""
|
||||
set -l out
|
||||
for line in $header
|
||||
set -l m (string match -r -- '^#\s+([A-Z][A-Z ]*[A-Z])\s*$' $line)
|
||||
if set -q m[2]
|
||||
set label $m[2]
|
||||
contains -- $label $skip; and continue
|
||||
set -l shown (string replace SYNOPSIS USAGE -- $label)
|
||||
set shown (string replace EXAMPLE EXAMPLES -- $shown)
|
||||
# One blank line before a heading, never two: the header's own
|
||||
# `#` separator has usually already emitted one.
|
||||
if set -q out[1]; and test -n (string trim -- "$out[-1]")
|
||||
set -a out ""
|
||||
end
|
||||
set -a out "$c_sec$shown$c_rst"
|
||||
continue
|
||||
end
|
||||
test -n "$label"; or continue
|
||||
contains -- $label $skip; and continue
|
||||
set -l body (string sub -s 2 -- $line)
|
||||
if string match -q ' *' -- $body
|
||||
set -a out " "(string sub -s 4 -- $body)
|
||||
else
|
||||
set -a out (string trim -- $body)
|
||||
end
|
||||
end
|
||||
|
||||
# Trim the trailing blank separator, mirroring
|
||||
# manualtools._trailing_blanks.
|
||||
while set -q out[-1]; and test -z (string trim -- "$out[-1]")
|
||||
set -e out[-1]
|
||||
end
|
||||
|
||||
echo "$c_ttl$name$c_rst"
|
||||
if test (count $out) -eq 0
|
||||
echo $miss
|
||||
else
|
||||
# out[1] is always a heading -- a body line cannot precede the
|
||||
# first label -- so this blank is never doubled.
|
||||
echo ""
|
||||
printf '%s\n' $out
|
||||
end
|
||||
return 0
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# __fish_palette
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Defines the shared terminal-output colour roles used by the
|
||||
# user-facing functions in this configuration. Declared
|
||||
# --no-scope-shadowing and using a bare `set`, so the variables are
|
||||
# created in the CALLER's scope -- a consumer just calls it and then
|
||||
# interpolates $c_head, $c_err and friends exactly as it did when the
|
||||
# declarations were inline.
|
||||
#
|
||||
# Call it where the local declarations used to sit, once per contiguous
|
||||
# block that needs the palette. set_color runs at call time, so the
|
||||
# values track $TERM exactly as inline declarations did. (Measured:
|
||||
# set_color output is identical across every TERM tested except
|
||||
# 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 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
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 always
|
||||
#
|
||||
# EXAMPLE
|
||||
# function mytool
|
||||
# __fish_palette
|
||||
# echo "$c_head""Usage:$c_reset $c_cmd""mytool$c_reset"
|
||||
# end
|
||||
#
|
||||
# NOTES
|
||||
# Calling this at top level (outside any function) creates GLOBAL
|
||||
# variables. Every consumer calls it from inside a function, where the
|
||||
# variables stay function-local and do not leak.
|
||||
#
|
||||
# functions/fish_prompt.fish deliberately does NOT use this palette. Its
|
||||
# c_* values are Catppuccin hex strings passed as ARGUMENTS to set_color
|
||||
# (`set_color --bold $c_green`), not captured escape sequences -- colour
|
||||
# inputs rather than rendered output, a different concern.
|
||||
|
||||
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_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)
|
||||
end
|
||||
@@ -8,7 +8,7 @@
|
||||
# __fish_user_dots_link
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Manages the git-ignored `user-dots` convenience symlink in the fish config
|
||||
# Manages the git-ignored user-dots convenience symlink in the fish config
|
||||
# directory ($__fish_config_dir/user-dots), pointing it at the resolved
|
||||
# $__fish_user_dots_path so the private overlay can be browsed from
|
||||
# ~/.config/fish/.
|
||||
@@ -34,7 +34,7 @@
|
||||
function __fish_user_dots_link --description 'Manage the user-dots convenience symlink'
|
||||
set -l link "$__fish_config_dir/user-dots"
|
||||
set -q __fish_user_dots_path
|
||||
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
|
||||
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
|
||||
|
||||
# Explicit opt-out: remove our symlink (never a real file/dir) and stop.
|
||||
__fish_variable_check __fish_user_dots_symlink
|
||||
@@ -49,7 +49,7 @@ function __fish_user_dots_link --description 'Manage the user-dots convenience s
|
||||
test -d "$__fish_user_dots_path"; or return 0
|
||||
if test -L "$link"
|
||||
test (readlink "$link") != "$__fish_user_dots_path"
|
||||
and ln -sfn "$__fish_user_dots_path" "$link"
|
||||
and ln -sfn "$__fish_user_dots_path" "$link"
|
||||
else if not test -e "$link"
|
||||
ln -s "$__fish_user_dots_path" "$link"
|
||||
end
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
function __insert_previous_path_head
|
||||
# Get the last command tokens
|
||||
set -l tokens (string split -n " " -- $history[1])
|
||||
|
||||
|
||||
# If there are tokens, take the last one and strip the 'tail'
|
||||
if set -q tokens[-1]
|
||||
set -l path_head (dirname -- $tokens[-1])
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
function __interactive_history_sub
|
||||
set -l current_line (commandline -b)
|
||||
set -l last_cmd $history[1]
|
||||
|
||||
|
||||
if string match -qr '(.+)/(.+)' -- "$current_line"
|
||||
set -l parts (string split '/' -- "$current_line")
|
||||
set -l old $parts[1]
|
||||
set -l new $parts[2]
|
||||
set -l history_index 1
|
||||
|
||||
|
||||
if test (count $parts) -ge 3; and string match -qr '^[1-9][0-9]*$' -- "$parts[3]"
|
||||
set history_index $parts[3]
|
||||
end
|
||||
|
||||
|
||||
set -l target_cmd $history[$history_index]
|
||||
|
||||
|
||||
if test -n "$target_cmd"
|
||||
set -l expanded (string replace -a -- "$old" "$new" "$target_cmd")
|
||||
commandline -r "$expanded"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# __jobrunner_sessions [<tool>]
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Parses `tmux list-sessions` or `screen -ls` into machine-readable rows,
|
||||
# Parses tmux list-sessions or screen -ls into machine-readable rows,
|
||||
# one per active session: name, PID, state, and start time separated by tabs.
|
||||
# Shared by jobrunner and its completions so both agree on what a session is
|
||||
# named. Prints nothing when no sessions exist.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Succeeds (returns 0) when the top-level kitty.conf contains an active
|
||||
# (non-commented) `watcher` directive — whether the fish-config managed one or
|
||||
# (non-commented) watcher directive — whether the fish-config managed one or
|
||||
# a user's own. Used to suppress the setup reminder and to inform status.
|
||||
#
|
||||
# EXIT STATUS
|
||||
|
||||
@@ -16,21 +16,21 @@
|
||||
function __substitute_typo
|
||||
set -l cursor_pos (commandline -C)
|
||||
set -l cmd (commandline)
|
||||
|
||||
|
||||
# Check if the current line matches the ^old^new pattern
|
||||
if string match -qr '\^([^^]+)\^([^^]*)' -- "$cmd"
|
||||
set -l last_cmd $history[1]
|
||||
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$cmd")
|
||||
set -l old $captured[2]
|
||||
set -l new $captured[3]
|
||||
|
||||
|
||||
if test -n "$old"
|
||||
set -l expanded (string replace -a -- "$old" "$new" "$last_cmd")
|
||||
commandline -r "$expanded"
|
||||
# No need to move cursor, it's a whole new line
|
||||
end
|
||||
else
|
||||
# If it's just a normal caret (not part of a pattern), just insert it
|
||||
end
|
||||
else
|
||||
# If it's just a normal caret (not part of a pattern), just insert it
|
||||
commandline -i '^'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Appends any patterns not already covered by the project's .gitignore.
|
||||
# Uses `git check-ignore` for accurate rule matching (catches wildcards
|
||||
# Uses git check-ignore for accurate rule matching (catches wildcards
|
||||
# and parent-dir globs). Falls back to a plain string search when the
|
||||
# root is not a git repository. Leading `/` is stripped from each pattern
|
||||
# root is not a git repository. Leading / is stripped from each pattern
|
||||
# before the path-based check so root-anchored patterns (e.g. /AGENTS.md)
|
||||
# are matched correctly.
|
||||
#
|
||||
@@ -31,16 +31,15 @@
|
||||
# EXAMPLE
|
||||
# _agents_init_ensure_gitignore /home/user/myproject "agents-init" "AGENTS/" "/AGENTS.md"
|
||||
function _agents_init_ensure_gitignore
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_reset (set_color normal)
|
||||
__fish_palette
|
||||
|
||||
if test (count $argv) -lt 3
|
||||
echo (set_color red)"_agents_init_ensure_gitignore: requires <root> <label> <pattern>..."(set_color normal) >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l root $argv[1]
|
||||
set -l label $argv[2]
|
||||
set -l root $argv[1]
|
||||
set -l label $argv[2]
|
||||
set -l patterns $argv[3..]
|
||||
set -l gitignore "$root/.gitignore"
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_repo_ensure_symlink <link> <target>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Idempotently makes <link> a symlink pointing at the directory <target>.
|
||||
#
|
||||
# Only directories are ever linked. The agent file-editing tools resolve a
|
||||
# symlinked directory transparently but refuse to write through a
|
||||
# symlinked file, so linking a file would silently break every later edit;
|
||||
# a non-directory target is refused outright.
|
||||
#
|
||||
# A missing target is refused rather than linked, because a dangling
|
||||
# memory/ symlink makes agent memory writes fail -- strictly worse than
|
||||
# having no backup at all.
|
||||
#
|
||||
# When <link> is an existing real directory, its contents are copied into
|
||||
# <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.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# link Path that should become the symlink
|
||||
# target Existing directory the link should point at
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Link is correct (created, repinned, or already right)
|
||||
# 1 Refused (non-directory target, missing target, non-directory link) or
|
||||
# a copy, remove, or link operation failed
|
||||
#
|
||||
# RETURNS
|
||||
# A single "→ ..." progress line on stdout when something changed;
|
||||
# nothing at all when the link was already correct.
|
||||
#
|
||||
# EXAMPLE
|
||||
# _agents_repo_ensure_symlink ~/.claude/projects/-home-u-proj/memory \
|
||||
# ~/.local/share/agent-vault/projects/host-user-proj/claude/memory
|
||||
function _agents_repo_ensure_symlink --argument-names link target
|
||||
test -n "$link" -a -n "$target"; or return 1
|
||||
|
||||
if test -e "$target"; and not test -d "$target"
|
||||
echo "_agents_repo_ensure_symlink: refusing non-directory target: $target" >&2
|
||||
return 1
|
||||
end
|
||||
if not test -d "$target"
|
||||
echo "_agents_repo_ensure_symlink: target does not exist: $target" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
if test -L "$link"
|
||||
set -l cur (path resolve "$link")
|
||||
set -l want (path resolve "$target")
|
||||
test "$cur" = "$want"; and return 0
|
||||
rm -f "$link"; or return 1
|
||||
else if test -d "$link"
|
||||
set -l contents (command ls -A "$link" 2>/dev/null)
|
||||
if test (count $contents) -gt 0
|
||||
command cp -rn "$link/." "$target/"; or return 1
|
||||
end
|
||||
rm -rf "$link"; or return 1
|
||||
else if test -e "$link"
|
||||
echo "_agents_repo_ensure_symlink: refusing to replace non-directory: $link" >&2
|
||||
return 1
|
||||
end
|
||||
|
||||
mkdir -p (path dirname "$link"); or return 1
|
||||
ln -s "$target" "$link"; or return 1
|
||||
echo "→ Linked "(path basename "$link")" → $target"
|
||||
end
|
||||
+15
-11
@@ -2,31 +2,34 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_init_install_tools <agents_dir>
|
||||
# _agents_repo_install_tools <repo_dir>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Copies the canonical version-bump script and git hook shims from
|
||||
# fish-config's scripts/agents-tools/ into <agents_dir>/.agents-tools/,
|
||||
# refreshing them when the shipped `agents-tools-version:` marker is newer
|
||||
# fish-config's scripts/agents-tools/ into <repo_dir>/.agents-tools/,
|
||||
# refreshing them when the shipped agents-tools-version: marker is newer
|
||||
# than the installed copy. Files are made executable. Idempotent: prints
|
||||
# nothing when the installed tooling is already current, or a short summary
|
||||
# line when it installed or updated the tooling.
|
||||
# line when it installed or updated the tooling, naming <repo_dir>'s own
|
||||
# basename rather than a hardcoded caller (e.g. "AGENTS/.agents-tools/" for
|
||||
# agents-init, "agent-vault/.agents-tools/" for agents-vault). Shared by
|
||||
# agents-init and agents-vault.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# agents_dir Absolute path to the AGENTS/ sub-repo root
|
||||
# repo_dir Absolute path to the git repo root to install tooling into
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Tooling is current or was installed/updated successfully
|
||||
# 1 Canonical source missing or a copy failed
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l msg (_agents_init_install_tools /path/to/AGENTS)
|
||||
# set -l msg (_agents_repo_install_tools /path/to/AGENTS)
|
||||
# test -n "$msg"; and echo $msg
|
||||
function _agents_init_install_tools --argument-names agents_dir
|
||||
test -n "$agents_dir"; or return 1
|
||||
function _agents_repo_install_tools --argument-names repo_dir
|
||||
test -n "$repo_dir"; or return 1
|
||||
set -l src (path resolve (status dirname)/../scripts/agents-tools)
|
||||
test -f "$src/version-bump"; or return 1
|
||||
set -l dest "$agents_dir/.agents-tools"
|
||||
set -l dest "$repo_dir/.agents-tools"
|
||||
|
||||
set -l want (command grep -m1 -oE 'agents-tools-version: *[0-9]+' "$src/version-bump" 2>/dev/null | command grep -oE '[0-9]+$')
|
||||
set -l have ""
|
||||
@@ -40,9 +43,10 @@ function _agents_init_install_tools --argument-names agents_dir
|
||||
command cp "$src/hooks/prepare-commit-msg" "$dest/hooks/prepare-commit-msg"; or return 1
|
||||
chmod +x "$dest/version-bump" "$dest/hooks/pre-commit" "$dest/hooks/prepare-commit-msg"; or return 1
|
||||
|
||||
set -l label (path basename -- "$repo_dir")
|
||||
if test -z "$have"
|
||||
echo "→ Installed AGENTS/.agents-tools/ (version-bump v$want)"
|
||||
echo "→ Installed $label/.agents-tools/ (version-bump v$want)"
|
||||
else
|
||||
echo "→ Updated AGENTS/.agents-tools/ (v$have → v$want)"
|
||||
echo "→ Updated $label/.agents-tools/ (v$have → v$want)"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_repo_local_slug <dir>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Builds the path-derived fallback slug used when a project has no git
|
||||
# remote: local-<sanitized-basename>-<8 hex of sha256(realpath)>. The
|
||||
# basename is lowercased and every character outside [a-z0-9._-] is
|
||||
# mapped to a dash, matching the sanitization the remote-URL branch of
|
||||
# _agents_repo_slug applies to hostnames and paths.
|
||||
#
|
||||
# This is the single source of truth for that formula. It exists so the
|
||||
# rule is written once: _agents_repo_slug's no-remote branch calls it to
|
||||
# produce the slug, and agents-vault's slug-migration fallback (used when
|
||||
# there is no live symlink yet to read the previous slug from) calls it
|
||||
# to recompute the same candidate. Duplicating the formula in both places
|
||||
# let them drift once before; this closes that gap for good.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# dir Absolute or relative path to the project directory
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Slug printed
|
||||
# 1 No directory argument given
|
||||
#
|
||||
# RETURNS
|
||||
# The local-* slug, one line on stdout.
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l slug (_agents_repo_local_slug /home/user/myproject)
|
||||
function _agents_repo_local_slug --argument-names dir
|
||||
test -n "$dir"; or return 1
|
||||
|
||||
set -l rp (path resolve "$dir")
|
||||
set -l base (string lower -- (path basename "$rp") | string replace -ra '[^a-z0-9._-]' '-')
|
||||
set -l digest (printf '%s' "$rp" | sha256sum | string split -f1 ' ')
|
||||
printf 'local-%s-%s\n' "$base" (string sub -l 8 -- "$digest")
|
||||
end
|
||||
@@ -0,0 +1,70 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# DEPENDENCIES
|
||||
# _agents_repo_local_slug
|
||||
#
|
||||
# SYNOPSIS
|
||||
# _agents_repo_slug <dir>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Derives the vault slug for a project directory. Prefers the normalized
|
||||
# git remote URL so the same project keys identically from any clone on
|
||||
# any machine; falls back to a path-derived key when no remote exists.
|
||||
#
|
||||
# Normalization strips the scheme, userinfo, and a numeric port, rewrites
|
||||
# scp-form host:path to host/path, drops a trailing .git, lowercases, and
|
||||
# maps every character outside [a-z0-9._-] to a dash. These all yield
|
||||
# git.rootiest.dev-rootiest-fish-config:
|
||||
#
|
||||
# https://git.rootiest.dev/rootiest/fish-config.git
|
||||
# git@git.rootiest.dev:rootiest/fish-config.git
|
||||
# ssh://git@git.rootiest.dev:22/rootiest/fish-config.git
|
||||
#
|
||||
# With no remote the slug is local-<sanitized-basename>-<8 hex of sha256(realpath)>,
|
||||
# where the basename is lowercased and mapped the same way as the remote form.
|
||||
# That key is machine-dependent by construction and is best-effort only;
|
||||
# agents-vault --adopt rebinds such an entry by hand.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# dir Absolute path to the project directory
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Slug printed
|
||||
# 1 No directory argument given
|
||||
#
|
||||
# RETURNS
|
||||
# The slug, one line on stdout.
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l slug (_agents_repo_slug /home/user/myproject)
|
||||
function _agents_repo_slug --argument-names dir
|
||||
test -n "$dir"; or return 1
|
||||
|
||||
set -l url (git -C "$dir" remote get-url origin 2>/dev/null)
|
||||
if test -z "$url"
|
||||
set -l remotes (git -C "$dir" remote 2>/dev/null)
|
||||
if test (count $remotes) -gt 0
|
||||
set url (git -C "$dir" remote get-url $remotes[1] 2>/dev/null)
|
||||
end
|
||||
end
|
||||
|
||||
if test -n "$url"
|
||||
set -l s $url
|
||||
# Order matters: the port must go before the scp-form rewrite, or
|
||||
# ssh://host:22/a/b becomes host/22/a/b and diverges from the
|
||||
# https slug for the same repository.
|
||||
set s (string replace -r '^[A-Za-z][A-Za-z0-9+.-]*://' '' -- $s)
|
||||
set s (string replace -r '^[^@/]+@' '' -- $s)
|
||||
set s (string replace -r '^([^/:]+):[0-9]+/' '$1/' -- $s)
|
||||
set s (string replace -r '^([^/:]+):' '$1/' -- $s)
|
||||
set s (string replace -r '\.git$' '' -- $s)
|
||||
set s (string replace -r '/+$' '' -- $s)
|
||||
set s (string lower -- $s)
|
||||
set s (string replace -ra '[^a-z0-9._-]' '-' -- $s)
|
||||
printf '%s\n' $s
|
||||
return 0
|
||||
end
|
||||
|
||||
_agents_repo_local_slug "$dir"
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_repo_sync <dir> <message>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Stages everything in <dir> and commits it with <message>. Shared by
|
||||
# agents-init and agents-vault.
|
||||
#
|
||||
# It never touches the network, and that is the point rather than an
|
||||
# omission. Both callers run on every agent launch, synchronously, ahead
|
||||
# of the agent itself, and a fetch there blocks the launch for as long as
|
||||
# an unreachable remote takes to time out and can prompt for credentials
|
||||
# invisibly underneath a starting agent. Committing needs no remote at
|
||||
# all -- only pushing does -- so the pull lives on agents-vault's push
|
||||
# path, which is already opt-in for exactly this reason. An offline
|
||||
# laptop therefore still gets a complete local backup, which is the whole
|
||||
# point of keeping one.
|
||||
#
|
||||
# A rebase already in progress is refused rather than committed: the
|
||||
# worktree then holds conflict markers, and recording those under a
|
||||
# routine-looking message buries the conflict in the history instead of
|
||||
# reporting it. The rebase is left exactly as it stands -- this function
|
||||
# did not start it, so it is not this function's to abort -- and the
|
||||
# caller says so.
|
||||
#
|
||||
# Commits are made with commit.gpgsign=false so a pinentry prompt can
|
||||
# never block a shell or an agent launch. If a pre-commit or commit-msg
|
||||
# hook rejects the commit (e.g. a secret scanner), that failure is
|
||||
# surfaced too: nothing is committed and a diagnostic goes to stderr.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# dir Absolute path to the git repository
|
||||
# message Commit subject used when there is something to commit
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Committed, or nothing needed committing
|
||||
# 1 <dir> is not a git repository, arguments were missing, or the commit
|
||||
# itself failed (e.g. a pre-commit/commit-msg hook rejected it)
|
||||
# 2 A rebase is in progress; nothing committed, nothing touched
|
||||
#
|
||||
# RETURNS
|
||||
# A single "→ Committed (<sha>) <subject>" line on stdout when it
|
||||
# commits; nothing when there was nothing to do.
|
||||
#
|
||||
# EXAMPLE
|
||||
# _agents_repo_sync /path/to/AGENTS "chore: sync AGENTS repository"
|
||||
function _agents_repo_sync --argument-names dir msg
|
||||
test -n "$dir" -a -n "$msg"; or return 1
|
||||
test -d "$dir/.git"; or return 1
|
||||
|
||||
# The guard above proved .git is a directory, so these are the same two
|
||||
# paths `agents-vault --status` reports an unresolved rebase from.
|
||||
if test -d "$dir/.git/rebase-merge"; or test -d "$dir/.git/rebase-apply"
|
||||
echo "_agents_repo_sync: unresolved rebase in $dir; nothing committed" >&2
|
||||
return 2
|
||||
end
|
||||
|
||||
git -C "$dir" add -A 2>/dev/null
|
||||
set -l status_out (git -C "$dir" status --porcelain 2>/dev/null)
|
||||
test -n "$status_out"; or return 0
|
||||
|
||||
if git -C "$dir" -c commit.gpgsign=false commit -q -m "$msg" 2>/dev/null
|
||||
set -l sha (git -C "$dir" rev-parse --short HEAD 2>/dev/null)
|
||||
set -l subject (git -C "$dir" log -1 --pretty=%s 2>/dev/null)
|
||||
echo "→ Committed ($sha) $subject"
|
||||
return 0
|
||||
else
|
||||
echo "_agents_repo_sync: commit failed in $dir (hook rejected it?); nothing committed" >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _agents_vault_dir
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Prints the agent memory vault root. Honors the universal variable
|
||||
# __fish_agent_vault_dir when set, otherwise
|
||||
# ${XDG_DATA_HOME:-$HOME/.local/share}/agent-vault.
|
||||
#
|
||||
# The vault holds agy state as well as Claude state, so it is not nested
|
||||
# under either tool's directory; it is backed-up state rather than
|
||||
# configuration, hence XDG_DATA_HOME rather than XDG_CONFIG_HOME.
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Always
|
||||
#
|
||||
# RETURNS
|
||||
# The vault root path, one line on stdout.
|
||||
#
|
||||
# EXAMPLE
|
||||
# set -l vault (_agents_vault_dir)
|
||||
function _agents_vault_dir
|
||||
if set -q __fish_agent_vault_dir; and test -n "$__fish_agent_vault_dir"
|
||||
printf '%s\n' "$__fish_agent_vault_dir"
|
||||
return 0
|
||||
end
|
||||
set -l base $XDG_DATA_HOME
|
||||
test -n "$base"; or set base "$HOME/.local/share"
|
||||
printf '%s\n' "$base/agent-vault"
|
||||
end
|
||||
@@ -18,13 +18,13 @@
|
||||
# conveniences (e.g. backs one wrapper
|
||||
# function) that only matter if you already
|
||||
# use that specific tool. Skipped by
|
||||
# `fish-deps install`/`sync` unless
|
||||
# `--optional` (or `--all`) is passed.
|
||||
# fish-deps install/sync unless
|
||||
# --optional (or --all) is passed.
|
||||
# term Terminal Emulators — GPU-accelerated terminal emulators
|
||||
# (kitty, wezterm) that only matter if one
|
||||
# of them is your actual terminal. Skipped
|
||||
# by `fish-deps install`/`sync` unless
|
||||
# `--terminals` (or `--all`) is passed.
|
||||
# by fish-deps install/sync unless
|
||||
# --terminals (or --all) is passed.
|
||||
# int Integrations — opt-in third-party services requiring
|
||||
# their own account/setup (wakatime,
|
||||
# tailscale).
|
||||
|
||||
@@ -75,14 +75,14 @@ function _fish_deps_install
|
||||
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
|
||||
if test -n "$_major"; and test "$_major" -lt 4
|
||||
set needs_install 1
|
||||
set upgrade_label "Upgrade"
|
||||
set upgrade_label Upgrade
|
||||
end
|
||||
end
|
||||
|
||||
if test $needs_install -eq 1
|
||||
set -l cargo_crate $_fdc_cargo[$i]
|
||||
set -l pm_pkg $_fdc_pm[$i]
|
||||
set -l special $_fdc_special[$i]
|
||||
set -l cargo_crate $_fdc_cargo[$i]
|
||||
set -l pm_pkg $_fdc_pm[$i]
|
||||
set -l special $_fdc_special[$i]
|
||||
|
||||
# Build list of available install methods
|
||||
set -l methods
|
||||
@@ -193,7 +193,9 @@ function _fish_deps_install
|
||||
echo " Available methods:"
|
||||
set -l m 1
|
||||
for lbl in $method_labels
|
||||
set_color brblack; echo -n " $m) "; set_color normal
|
||||
set_color brblack
|
||||
echo -n " $m) "
|
||||
set_color normal
|
||||
echo $lbl
|
||||
set m (math $m + 1)
|
||||
end
|
||||
@@ -202,7 +204,9 @@ function _fish_deps_install
|
||||
set chosen_method $methods[$_choice]
|
||||
end
|
||||
else
|
||||
set_color brblack; echo " "(string lower $upgrade_label)"ing via $method_labels[1]"; set_color normal
|
||||
set_color brblack
|
||||
echo " "(string lower $upgrade_label)"ing via $method_labels[1]"
|
||||
set_color normal
|
||||
end
|
||||
|
||||
# Execute chosen method
|
||||
@@ -329,14 +333,18 @@ function _fish_deps_install
|
||||
|
||||
if test $status -eq 0
|
||||
set installed_any 1
|
||||
set_color green; echo " $bin "(string lower $upgrade_label)"ed."; set_color normal
|
||||
set_color green
|
||||
echo " $bin "(string lower $upgrade_label)"ed."
|
||||
set_color normal
|
||||
if test "$bin" = fish
|
||||
set_color yellow
|
||||
echo " Fish upgraded — restart your shell to use the new version."
|
||||
set_color normal
|
||||
end
|
||||
else
|
||||
set_color red; echo " $bin "(string lower $upgrade_label)" failed."; set_color normal
|
||||
set_color red
|
||||
echo " $bin "(string lower $upgrade_label)" failed."
|
||||
set_color normal
|
||||
end
|
||||
end
|
||||
set i (math $i + 1)
|
||||
|
||||
@@ -23,35 +23,57 @@ function _fish_deps_status
|
||||
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
|
||||
if test -n "$_major"; and test "$_major" -lt 4
|
||||
set -l _ver (fish --version 2>&1 | string replace 'fish, ' '')
|
||||
set_color yellow; echo -n " ⚠ "; set_color normal
|
||||
set_color yellow
|
||||
echo -n " ⚠ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "($_ver — upgrade to 4.0+ required)"; set_color normal
|
||||
set_color brblack
|
||||
echo "($_ver — upgrade to 4.0+ required)"
|
||||
set_color normal
|
||||
return
|
||||
end
|
||||
end
|
||||
set_color green; echo -n " ✓ "; set_color normal
|
||||
set_color green
|
||||
echo -n " ✓ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Found at "(__fish_real_command $bin)")"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Found at "(__fish_real_command $bin)")"
|
||||
set_color normal
|
||||
else if test "$tier" = req
|
||||
set_color red; echo -n " ✗ "; set_color normal
|
||||
set_color red
|
||||
echo -n " ✗ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
else if test "$tier" = rec
|
||||
set_color yellow; echo -n " ⚠ "; set_color normal
|
||||
set_color yellow
|
||||
echo -n " ⚠ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
else
|
||||
# opt / term / int: absence is expected and not alarming
|
||||
set_color brblack; echo -n " – "; set_color normal
|
||||
set_color brblack
|
||||
echo -n " – "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
end
|
||||
end
|
||||
|
||||
for tier_label in "Required Dependencies:req" "Recommended Dependencies:rec" "Optional Dependencies:opt" "Terminal Emulators:term" "Integrations:int"
|
||||
set -l label (string split : $tier_label)[1]
|
||||
set -l tier (string split : $tier_label)[2]
|
||||
set_color cyan; echo $label; set_color normal
|
||||
set -l tier (string split : $tier_label)[2]
|
||||
set_color cyan
|
||||
echo $label
|
||||
set_color normal
|
||||
set -l i 1
|
||||
for bin in $_fdc_bins
|
||||
if test "$_fdc_tiers[$i]" = $tier
|
||||
|
||||
@@ -33,9 +33,9 @@ function _fish_deps_update
|
||||
continue
|
||||
end
|
||||
|
||||
set -l cargo_crate $_fdc_cargo[$i]
|
||||
set -l pm_pkg $_fdc_pm[$i]
|
||||
set -l special $_fdc_special[$i]
|
||||
set -l cargo_crate $_fdc_cargo[$i]
|
||||
set -l pm_pkg $_fdc_pm[$i]
|
||||
set -l special $_fdc_special[$i]
|
||||
|
||||
# yay: update via paru if available, else system PM
|
||||
if test "$special" = yay-build
|
||||
@@ -101,10 +101,14 @@ function _fish_deps_update
|
||||
echo "Updating $bin..."
|
||||
set -l _arch (uname -m)
|
||||
switch $_arch
|
||||
case x86_64; set _arch amd64
|
||||
case aarch64 arm64; set _arch arm64
|
||||
case armv7l; set _arch arm
|
||||
case '*'; set _arch amd64
|
||||
case x86_64
|
||||
set _arch amd64
|
||||
case aarch64 arm64
|
||||
set _arch arm64
|
||||
case armv7l
|
||||
set _arch arm
|
||||
case '*'
|
||||
set _arch amd64
|
||||
end
|
||||
set -l _zip "wakatime-cli-linux-$_arch.zip"
|
||||
set -l _bin_src "wakatime-cli-linux-$_arch"
|
||||
|
||||
@@ -46,7 +46,7 @@ function _fish_mkdir_p --description 'mkdir -p with configurable verbose output'
|
||||
while not test -d $cursor
|
||||
set -p to_create $cursor
|
||||
set -l up (dirname $cursor)
|
||||
test "$up" = "$cursor"; and break # filesystem root guard
|
||||
test "$up" = "$cursor"; and break # filesystem root guard
|
||||
set cursor $up
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _fish_source_scoped <file>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Sources <file> inside its own function-call boundary. `source` runs
|
||||
# in the caller's own scope, so a bare `return` in a sourced file --
|
||||
# used by several conf.d guards as an early exit -- would otherwise
|
||||
# unwind whatever function called `source` directly, not just the
|
||||
# sourced file. Calling through this helper contains it to here.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# file Path to the fish script to source
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 File does not exist (nothing to do)
|
||||
# Exit status of the sourced file otherwise
|
||||
#
|
||||
# EXAMPLE
|
||||
# _fish_source_scoped $__fish_config_dir/conf.d/paru-wrapper.fish
|
||||
function _fish_source_scoped --argument-names file
|
||||
test -f $file; or return 0
|
||||
source $file
|
||||
end
|
||||
@@ -9,7 +9,7 @@
|
||||
# by a desktop file manager (Dolphin, Nautilus, GNOME Videos, ...) in the
|
||||
# freedesktop thumbnail cache and renders it via _fzf_preview_image if
|
||||
# found. Otherwise falls back to ffprobe-formatted metadata (duration,
|
||||
# codec, resolution, tags) when ffprobe is installed, or plain `file`
|
||||
# codec, resolution, tags) when ffprobe is installed, or plain file
|
||||
# output as a last resort. Neither the thumbnail cache lookup nor ffprobe
|
||||
# are tracked in fish-deps: both are best-effort, matching how the
|
||||
# image-preview tool chain (kitten/chafa/viu/timg) is already handled.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _mkrep_say <silent> <message>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Prints <message> unless <silent> is 1. Used by mkrep for its default
|
||||
# per-step summary lines, which -s/--silent must suppress entirely.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# silent 1 to suppress output, 0 to print
|
||||
# message Text to print (echo -e, so escapes/colour codes render)
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 always
|
||||
function _mkrep_say --argument-names silent msg
|
||||
test "$silent" = 1; and return 0
|
||||
echo -e $msg
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _mkrep_verbose <silent> <verbose> <message>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Prints <message> only when <verbose> is 1 and <silent> is 0. Used by
|
||||
# mkrep for the extra step-by-step tracing -v/--verbose adds.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# silent 1 to suppress output, 0 to allow it
|
||||
# verbose 1 to print, 0 to stay quiet
|
||||
# message Text to print (echo -e, so escapes/colour codes render)
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 always
|
||||
function _mkrep_verbose --argument-names silent verbose msg
|
||||
test "$silent" = 1; and return 0
|
||||
test "$verbose" = 1; or return 0
|
||||
echo -e $msg
|
||||
end
|
||||
@@ -11,9 +11,9 @@
|
||||
# modification time, so the most recently written logs are kept — actively
|
||||
# appended logs (e.g. a tmux pipe-pane stream) survive.
|
||||
#
|
||||
# Uses `command ls`/`command rm` to bypass the C1 shadows: the bare `ls` is
|
||||
# Uses command ls/command rm to bypass the C1 shadows: the bare ls is
|
||||
# the eza wrapper, which injects OSC-8 hyperlink escapes into paths, and the
|
||||
# bare `rm` is the trash wrapper. The glob is expanded via `set` first so a
|
||||
# bare rm is the trash wrapper. The glob is expanded via set first so a
|
||||
# no-match (empty dir / first run) yields an empty list instead of a hard
|
||||
# "No matches for wildcard" error.
|
||||
#
|
||||
|
||||
@@ -7,4 +7,3 @@ function _puffer_fish_expand_bang
|
||||
commandline --insert '!'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ function _puffer_fish_expand_star
|
||||
else if string match --quiet -- '!' "$(commandline --current-token)"
|
||||
set -l prev_cmd $history[1]
|
||||
set -l prev_args (string split ' ' $prev_cmd)
|
||||
set -e prev_args[1] # remove command name
|
||||
set -e prev_args[1] # remove command name
|
||||
set -l arg_str (string join ' ' $prev_args)
|
||||
# replace !* with all arguments
|
||||
commandline --current-token ''
|
||||
|
||||
@@ -28,9 +28,9 @@ function _smart_execute --description 'Execute different functions based on the
|
||||
# If it ends in =, run qalc; fall back to normal execute if qalc is absent
|
||||
_qalc_eval; or commandline -f execute
|
||||
|
||||
# case 'g *'
|
||||
# # EXAMPLE FUTURE EXTENSION
|
||||
# _some_git_helper
|
||||
# case 'g *'
|
||||
# # EXAMPLE FUTURE EXTENSION
|
||||
# _some_git_helper
|
||||
|
||||
case '*'
|
||||
# Default: execute the command line as-is
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# Captures the current Zellij pane's scrollback to a timestamped log in
|
||||
# SCROLLBACK_HISTORY_DIR (default ~/.terminal_history). Zellij has no live
|
||||
# output-streaming facility like tmux's pipe-pane, so this performs a one-shot
|
||||
# `zellij action dump-screen --full` — intended to run on shell exit. Old
|
||||
# zellij action dump-screen --full — intended to run on shell exit. Old
|
||||
# zellij_*.log files are pruned via _prune_terminal_logs to stay within
|
||||
# SCROLLBACK_HISTORY_MAX_FILES.
|
||||
#
|
||||
|
||||
+63
-63
@@ -4,6 +4,9 @@
|
||||
# CATEGORY
|
||||
# 12-ai-and-developer-tools
|
||||
#
|
||||
# DEPENDENCIES
|
||||
# _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
|
||||
#
|
||||
# SYNOPSIS
|
||||
# agents-init [-a | --agents] [-p | --plugins] [-v | --verbose]
|
||||
# [-q | --quiet] [-s | --silent] [-h | --help]
|
||||
@@ -50,11 +53,16 @@
|
||||
#
|
||||
# With no flags, runs both --agents and --plugins setup; --agents re-runs
|
||||
# only the AGENTS.md / symlink step and --plugins only the plans/specs/
|
||||
# devlogs wiring step. Managed paths are added to .gitignore. The sub-repo
|
||||
# is pulled first when it has an upstream, and at the end of every
|
||||
# invocation any uncommitted changes inside it are auto-committed so
|
||||
# agent-made edits are captured automatically. Fully idempotent: a second
|
||||
# run produces no output and no new commits.
|
||||
# devlogs wiring step. Managed paths are added to .gitignore. At the end
|
||||
# of every invocation any uncommitted changes inside the sub-repo are
|
||||
# auto-committed so agent-made edits are captured automatically. Fully
|
||||
# idempotent: a second run produces no output and no new commits.
|
||||
#
|
||||
# The commit is local only. Nothing here fetches or pushes: the wrappers
|
||||
# call this synchronously before starting an agent, and a network round
|
||||
# trip there blocks the launch until an unreachable remote times out and
|
||||
# can prompt for credentials with nobody watching. A sub-repo that has an
|
||||
# upstream is pulled by hand, on the user's own schedule.
|
||||
#
|
||||
# Called automatically by the claude and agy wrappers on every invocation.
|
||||
#
|
||||
@@ -68,7 +76,8 @@
|
||||
#
|
||||
# EXIT STATUS
|
||||
# 0 Setup completed successfully
|
||||
# 1 Fatal error (git init failed, move failed, etc.)
|
||||
# 1 Fatal error (git init failed, move failed, the AGENTS/ commit was
|
||||
# rejected, or an unresolved rebase blocked it)
|
||||
#
|
||||
# EXAMPLE
|
||||
# agents-init
|
||||
@@ -76,14 +85,7 @@
|
||||
# agents-init --plugins
|
||||
# agents-init --quiet
|
||||
function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec files and plugin dirs'
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_cmd (set_color --bold)
|
||||
set -l c_flag (set_color yellow)
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_warn (set_color yellow)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_reset (set_color normal)
|
||||
__fish_palette
|
||||
|
||||
argparse h/help a/agents p/plugins v/verbose q/quiet s/silent -- $argv
|
||||
or return 1
|
||||
@@ -165,7 +167,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
||||
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/.version (1.0.0)$c_reset"
|
||||
end
|
||||
|
||||
set -l _tools (_agents_init_install_tools "$agents_dir")
|
||||
set -l _tools (_agents_repo_install_tools "$agents_dir")
|
||||
if test -n "$_tools"
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok$_tools$c_reset"
|
||||
@@ -259,38 +261,26 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
||||
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
|
||||
end
|
||||
|
||||
# ── Root symlink: AGENTS.md → AGENTS/AGENTS.md ───────────────────────
|
||||
set -l _need_link 0
|
||||
if not test -L "$root/AGENTS.md"
|
||||
set _need_link 1
|
||||
else if test (readlink "$root/AGENTS.md") != AGENTS/AGENTS.md
|
||||
rm -f "$root/AGENTS.md"
|
||||
set _need_link 1
|
||||
end
|
||||
if test $_need_link -eq 1
|
||||
if not ln -s AGENTS/AGENTS.md "$root/AGENTS.md"
|
||||
echo "$c_err""Error: could not create AGENTS.md symlink$c_reset" >&2
|
||||
return 1
|
||||
# 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
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS.md → AGENTS/AGENTS.md$c_reset"
|
||||
end
|
||||
|
||||
# ── Root symlink: CLAUDE.md → AGENTS/CLAUDE.md ───────────────────────
|
||||
set -l _need_link 0
|
||||
if not test -L "$root/CLAUDE.md"
|
||||
set _need_link 1
|
||||
else if test (readlink "$root/CLAUDE.md") != AGENTS/CLAUDE.md
|
||||
rm -f "$root/CLAUDE.md"
|
||||
set _need_link 1
|
||||
end
|
||||
if test $_need_link -eq 1
|
||||
if not ln -s AGENTS/CLAUDE.md "$root/CLAUDE.md"
|
||||
echo "$c_err""Error: could not create CLAUDE.md symlink$c_reset" >&2
|
||||
return 1
|
||||
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
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok→ Linked CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
|
||||
end
|
||||
|
||||
# ── .gitignore ────────────────────────────────────────────────────────
|
||||
@@ -461,24 +451,30 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
||||
end
|
||||
|
||||
# ──────────────────────── Auto-commit AGENTS/ ────────────────────────────
|
||||
# Pull first when an upstream is configured so the local .version reflects
|
||||
# any remote bumps before we add to it (no-op for local-only repos).
|
||||
if git -C "$agents_dir" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||
git -C "$agents_dir" pull --rebase --autostash -q 2>/dev/null
|
||||
end
|
||||
git -C "$agents_dir" add -A 2>/dev/null
|
||||
set -l status_out (git -C "$agents_dir" status --porcelain 2>/dev/null)
|
||||
if test -n "$status_out"
|
||||
set -l msg "chore: sync AGENTS repository"
|
||||
test $did_init -eq 1; and set msg "chore: initialize AGENTS repository"
|
||||
if git -C "$agents_dir" -c commit.gpgsign=false commit -q -m "$msg" 2>/dev/null
|
||||
set changed 1
|
||||
if test $verbose -eq 1
|
||||
set -l sha (git -C "$agents_dir" rev-parse --short HEAD 2>/dev/null)
|
||||
set -l realmsg (git -C "$agents_dir" log -1 --pretty=%s 2>/dev/null)
|
||||
echo "$c_ok→ Committed AGENTS/ ($sha) $c_dim$realmsg$c_reset"
|
||||
end
|
||||
end
|
||||
# Purely local: no fetch, no push. This function runs synchronously on
|
||||
# every agent launch, and a network round trip there blocks the launch
|
||||
# for as long as an unreachable remote takes to time out. Committing
|
||||
# never needed one -- see _agents_repo_sync.
|
||||
#
|
||||
# Every way the commit can fail is an arm of its own. A sync that did
|
||||
# not commit means agent-made edits were not captured, so it is a
|
||||
# failure rather than a line to walk past -- and the missing `-ne 0`
|
||||
# arm was not a cosmetic gap: fish resolves a branchless `if` to 0, so
|
||||
# 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"
|
||||
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
|
||||
echo "$c_warn→ AGENTS/ has an unresolved rebase; nothing committed$c_reset" >&2
|
||||
set failed 1
|
||||
else if test $sync_rc -ne 0
|
||||
echo "$c_err""Error: the AGENTS/ commit failed; nothing recorded$c_reset" >&2
|
||||
set failed 1
|
||||
else if test -n "$sync_out"
|
||||
set changed 1
|
||||
test $verbose -eq 1; and echo "$c_ok$sync_out$c_reset"
|
||||
end
|
||||
|
||||
# Quiet summary: one line at the end, only if something actually changed
|
||||
@@ -489,4 +485,8 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
||||
echo "$c_ok→ Synced AGENTS scaffolding$c_reset"
|
||||
end
|
||||
end
|
||||
|
||||
# Explicit, because the branchless `if` above resolves to 0 and would
|
||||
# otherwise be this function's exit status.
|
||||
test $failed -eq 0
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+33
-11
@@ -8,7 +8,7 @@
|
||||
# aliases/dev-tools
|
||||
#
|
||||
# DEPENDENCIES
|
||||
# agents-init
|
||||
# agents-init, agents-vault
|
||||
#
|
||||
# SYNOPSIS
|
||||
# agy [ARGS...]
|
||||
@@ -18,16 +18,26 @@
|
||||
# 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/ is scaffolded and CLAUDE.md
|
||||
# is symlinked to AGENTS/AGENTS.md in the current project. Arguments are
|
||||
# forwarded verbatim to the real agy binary, except for -r/--resume which
|
||||
# are translated to -c/--continue.
|
||||
# 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
|
||||
# rather than at session end.
|
||||
#
|
||||
# Arguments are forwarded verbatim to the real agy binary, except for
|
||||
# -r/--resume which use different syntax in agy than claude: bare
|
||||
# -r/--resume (no session id following) translate to -c/--continue
|
||||
# (resume most-recent session); -r/--resume given a session id (via
|
||||
# =id or a following bare word) translate to --conversation(=id)
|
||||
# (open that specific session).
|
||||
#
|
||||
# Opinionated component (C1): when disabled via __fish_config_op_aliases
|
||||
# (or the __fish_config_opinionated master), the command is passed through
|
||||
# to the real agy binary unchanged.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# ARGS Arguments forwarded to the underlying agy binary (-r translates to -c)
|
||||
# ARGS Arguments forwarded to the underlying agy binary (-r/--resume
|
||||
# translate to -c/--continue or --conversation, see DESCRIPTION)
|
||||
#
|
||||
# EXIT STATUS
|
||||
# Exit status of the underlying agy binary
|
||||
@@ -35,6 +45,7 @@
|
||||
# EXAMPLE
|
||||
# agy
|
||||
# agy --resume
|
||||
# agy --resume=5fffb251-2cd6-4cfe-8dac-b5e913a86db6
|
||||
# agy -i "initial prompt"
|
||||
# agy models
|
||||
function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ sub-repo before launch'
|
||||
@@ -44,14 +55,25 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su
|
||||
end
|
||||
|
||||
agents-init --quiet
|
||||
agents-vault --quiet
|
||||
|
||||
for i in (seq (count $argv))
|
||||
if test "$argv[$i]" = "-r"
|
||||
set argv[$i] "-c"
|
||||
else if test "$argv[$i]" = "--resume"
|
||||
set argv[$i] "--continue"
|
||||
else if string match -q -- "--resume=*" "$argv[$i]"
|
||||
set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]")
|
||||
switch "$argv[$i]"
|
||||
case -r --resume
|
||||
# Session id given as next bare word (not a flag) -> --conversation.
|
||||
# Nothing follows, or next word is a flag -> resume most-recent (-c/--continue).
|
||||
set -l next (math $i + 1)
|
||||
if test $next -le (count $argv); and not string match -q -- '-*' "$argv[$next]"
|
||||
set argv[$i] --conversation
|
||||
else if test "$argv[$i]" = -r
|
||||
set argv[$i] -c
|
||||
else
|
||||
set argv[$i] --continue
|
||||
end
|
||||
case '-r=*'
|
||||
set argv[$i] (string replace -- '-r=' '--conversation=' "$argv[$i]")
|
||||
case '--resume=*'
|
||||
set argv[$i] (string replace -- '--resume=' '--conversation=' "$argv[$i]")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -42,14 +42,7 @@
|
||||
# auto-pull list
|
||||
# auto-pull remove qmk_firmware
|
||||
function auto-pull --description 'Manage the auto-pull repository registry'
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_cmd (set_color --bold)
|
||||
set -l c_flag (set_color yellow)
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_warn (set_color yellow)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_reset (set_color normal)
|
||||
__fish_palette
|
||||
|
||||
set -q __fish_config_dir; or set -l __fish_config_dir $XDG_CONFIG_HOME/fish
|
||||
set -q __fish_user_dots_path; or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user