Compare commits
40
Commits
fd64335207
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa617ef455
|
||
|
|
c82132aed0
|
||
|
|
15d3a7e4dc
|
||
|
|
ec700cece1
|
||
|
|
86aea9d5ef
|
||
|
|
1f3c8f41e7
|
||
|
|
911c6e95ed
|
||
|
|
8b08b4d5e5
|
||
|
|
1d714a84d0
|
||
|
|
e01e83bd36
|
||
|
|
82bea2f539
|
||
|
|
de92277226
|
||
|
|
2baa91f275
|
||
|
|
6a410a0fce
|
||
|
|
c3f134b5b4
|
||
|
|
4e5f0078ce
|
||
|
|
4240a04754
|
||
|
|
ecfb93a818
|
||
|
|
a776d4d12d
|
||
|
|
731661b581
|
||
|
|
c48af2fa23
|
||
|
|
028478940f
|
||
|
|
27b6f5d263
|
||
|
|
59ace6fe6b
|
||
|
|
344ee60acd
|
||
|
|
1b9f1558c4
|
||
|
|
ea1575f902
|
||
|
|
4797af85f3
|
||
|
|
6074687a80
|
||
|
|
7e3d48ddac
|
||
|
|
37fea155b3
|
||
|
|
355688c134
|
||
|
|
9e8d29cc30
|
||
|
|
af65092102
|
||
|
|
35a48ac868
|
||
|
|
8dcbc62359
|
||
|
|
17a95abebe
|
||
|
|
b03ba7490e
|
||
|
|
3bbda31eff
|
||
|
|
fceddfc43f
|
+136
-12
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths: &ci-paths
|
||||||
- "docs/manual/**"
|
- "docs/manual/**"
|
||||||
- "docs/build-manual.py"
|
- "docs/build-manual.py"
|
||||||
- "docs/manualtools.py"
|
- "docs/manualtools.py"
|
||||||
@@ -16,6 +16,17 @@ on:
|
|||||||
- "completions/**"
|
- "completions/**"
|
||||||
- "integrations/**"
|
- "integrations/**"
|
||||||
- "tests/**"
|
- "tests/**"
|
||||||
|
- "scripts/**"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
# No `paths:` filter here (unlike push, above): a PR carrying
|
||||||
|
# Kind/Testing, Area/Docs, etc. must still trigger this workflow even
|
||||||
|
# when its diff touches nothing in ci-paths, or the label-based gates
|
||||||
|
# in the test/docs jobs below would never get a chance to evaluate.
|
||||||
|
# `labeled`/`unlabeled` cover a label added after the PR is already
|
||||||
|
# open, without a new commit.
|
||||||
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
job:
|
job:
|
||||||
@@ -26,7 +37,7 @@ on:
|
|||||||
options:
|
options:
|
||||||
- all
|
- all
|
||||||
- test
|
- test
|
||||||
- build-docs
|
- docs
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# This workflow file is mirrored to GitHub as-is, but the runner label
|
# This workflow file is mirrored to GitHub as-is, but the runner label
|
||||||
@@ -44,8 +55,41 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITEA_TOKEN }}
|
token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Runs unconditionally and always sets an output, so every step
|
||||||
|
# after it can gate on a single `steps.relevance.outputs.run`
|
||||||
|
# check instead of repeating the label/path OR-chain everywhere.
|
||||||
|
# push/workflow_dispatch are always relevant -- push is already
|
||||||
|
# path-filtered above, and a manual dispatch is explicit intent.
|
||||||
|
# A pull_request is relevant if it carries a testing-related label
|
||||||
|
# (independent of what it touches -- see the `on.pull_request`
|
||||||
|
# comment above) or if its diff touches a ci-paths pattern (the
|
||||||
|
# same list the push trigger above filters on; duplicated here in
|
||||||
|
# shell glob form since a PR event isn't pre-filtered by paths).
|
||||||
|
- name: Determine relevance
|
||||||
|
id: relevance
|
||||||
|
env:
|
||||||
|
PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" != "pull_request" ]; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if printf '%s' "$PR_LABELS" | grep -qE '"name":[[:space:]]*"(Kind/Testing|Area/Tests|Area/CI|Area/Scripts)"'; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git fetch origin "${{ github.event.pull_request.base.ref }}"
|
||||||
|
if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}...HEAD" \
|
||||||
|
| grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/|functions/|conf\.d/|config\.fish$|completions/|integrations/|tests/|scripts/)'; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "run=false" >>"$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install fish
|
- name: Install fish
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -o Acquire::Retries=3 update -qq
|
sudo apt-get -o Acquire::Retries=3 update -qq
|
||||||
# apt-utils, so debconf has a target for the "delaying package
|
# apt-utils, so debconf has a target for the "delaying package
|
||||||
@@ -63,16 +107,27 @@ jobs:
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y fish
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y fish
|
||||||
|
|
||||||
- name: Run fish config tests
|
- name: Run fish config tests
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: fish tests/run-tests.fish
|
run: fish tests/run-tests.fish
|
||||||
|
|
||||||
build-docs:
|
# Documentation tests/build, and (push/dispatch only) publish. Split
|
||||||
needs: test
|
# into two sections within one job rather than two jobs: the publish
|
||||||
|
# steps need the files the build steps just generated, and passing
|
||||||
|
# those between separate jobs would need upload/download-artifact for
|
||||||
|
# no real benefit here.
|
||||||
|
#
|
||||||
|
# Does NOT `need: test` (the old build-docs job did, gating publish
|
||||||
|
# on it). main now has branch protection requiring the test job to
|
||||||
|
# pass before a PR can merge, so by the time a push-to-main reaches
|
||||||
|
# this job, test has already passed as a condition of getting here --
|
||||||
|
# re-checking it in-workflow would be redundant. The one gap that
|
||||||
|
# leaves is a direct admin push bypassing the PR flow entirely; that's
|
||||||
|
# the same trust already extended by leaving block_admin_merge_override
|
||||||
|
# off on the branch protection rule, not a new hole.
|
||||||
|
docs:
|
||||||
if: |
|
if: |
|
||||||
github.server_url != 'https://github.com' &&
|
github.server_url != 'https://github.com' &&
|
||||||
always() &&
|
(github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all' || github.event.inputs.job == 'docs')
|
||||||
(github.event.inputs.job == 'build-docs' ||
|
|
||||||
((github.event_name != 'workflow_dispatch' || github.event.inputs.job == 'all') &&
|
|
||||||
needs.test.result == 'success'))
|
|
||||||
runs-on: racknerd-mini
|
runs-on: racknerd-mini
|
||||||
env:
|
env:
|
||||||
# Silences Node's internal "punycode module is deprecated" notice
|
# Silences Node's internal "punycode module is deprecated" notice
|
||||||
@@ -83,9 +138,43 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITEA_TOKEN }}
|
# The default GITEA_TOKEN is Gitea Actions' built-in synthetic
|
||||||
|
# bot identity, not a real account -- main's branch protection
|
||||||
|
# rejects its pushes outright (run 983, run 990), and it can't
|
||||||
|
# be whitelisted because it isn't an addable user. BOT_PUSH_TOKEN
|
||||||
|
# is a PAT on the rootiest account (already bypass-whitelisted)
|
||||||
|
# used only so this job's later push succeeds; commit authorship
|
||||||
|
# and GPG signing below still use the fishconfig-bot identity,
|
||||||
|
# which is unrelated to push auth.
|
||||||
|
token: ${{ secrets.BOT_PUSH_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# Same shape as the test job's identical step; see its comment.
|
||||||
|
# Only the label set and path patterns differ, narrowed to the
|
||||||
|
# docs-specific subset of ci-paths.
|
||||||
|
- name: Determine relevance
|
||||||
|
id: relevance
|
||||||
|
env:
|
||||||
|
PR_LABELS: ${{ toJSON(github.event.pull_request.labels) }}
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" != "pull_request" ]; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if printf '%s' "$PR_LABELS" | grep -qE '"name":[[:space:]]*"(Kind/Documentation|Area/Docs)"'; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git fetch origin "${{ github.event.pull_request.base.ref }}"
|
||||||
|
if git diff --name-only "origin/${{ github.event.pull_request.base.ref }}...HEAD" \
|
||||||
|
| grep -qE '^(docs/manual/|docs/build-manual\.py$|docs/manualtools\.py$|docs/verify-manual\.py$|docs/site/)'; then
|
||||||
|
echo "run=true" >>"$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "run=false" >>"$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get -o Acquire::Retries=3 update -qq
|
sudo apt-get -o Acquire::Retries=3 update -qq
|
||||||
# apt-utils: see the "Install fish" step's identical comment in
|
# apt-utils: see the "Install fish" step's identical comment in
|
||||||
@@ -97,6 +186,7 @@ jobs:
|
|||||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y pandoc python3-yaml fish
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y pandoc python3-yaml fish
|
||||||
|
|
||||||
- name: Generate concatenated markdown
|
- name: Generate concatenated markdown
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: python3 docs/build-manual.py --concat -o docs/fish-config.md
|
run: python3 docs/build-manual.py --concat -o docs/fish-config.md
|
||||||
|
|
||||||
# Regeneration MUST run before verification: verify-manual.py's
|
# Regeneration MUST run before verification: verify-manual.py's
|
||||||
@@ -104,13 +194,19 @@ jobs:
|
|||||||
# against docs/fish-config.md on disk. Before this step ran, that
|
# against docs/fish-config.md on disk. Before this step ran, that
|
||||||
# file was still the stale pre-push copy, so any ordinary edit under
|
# file was still the stale pre-push copy, so any ordinary edit under
|
||||||
# docs/manual/** failed the round-trip check before anything was
|
# docs/manual/** failed the round-trip check before anything was
|
||||||
# regenerated. Do not reorder this back — verification still gates
|
# regenerated. Do not reorder this back -- verification still gates
|
||||||
# pandoc and the auto-commit below, it just no longer requires a
|
# pandoc and the auto-commit below, it just no longer requires a
|
||||||
# contributor to hand-sync the generated file before pushing.
|
# contributor to hand-sync the generated file before pushing.
|
||||||
|
#
|
||||||
|
# This is the "documentation tests" section: on a PR, it runs
|
||||||
|
# (and can fail the job) whenever relevant, without needing the
|
||||||
|
# publish steps below to run at all.
|
||||||
- name: Verify manual integrity
|
- name: Verify manual integrity
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: python3 docs/verify-manual.py
|
run: python3 docs/verify-manual.py
|
||||||
|
|
||||||
- name: Compile man page
|
- name: Compile man page
|
||||||
|
if: steps.relevance.outputs.run == 'true'
|
||||||
run: |
|
run: |
|
||||||
pandoc --standalone \
|
pandoc --standalone \
|
||||||
--from markdown \
|
--from markdown \
|
||||||
@@ -118,21 +214,30 @@ jobs:
|
|||||||
docs/fish-config.md \
|
docs/fish-config.md \
|
||||||
-o docs/fish-config.1
|
-o docs/fish-config.1
|
||||||
|
|
||||||
|
# ──────────────────────── Publish only ───────────────────────
|
||||||
|
# Everything below deploys the production site and commits
|
||||||
|
# generated files straight to the checked-out branch. Never runs
|
||||||
|
# from a pull_request -- PR content isn't main yet, and a
|
||||||
|
# fork/branch push shouldn't touch prod.
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
node-version: "24"
|
||||||
|
|
||||||
- name: Generate site content
|
- name: Generate site content
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
run: python3 docs/build-manual.py --site
|
run: python3 docs/build-manual.py --site
|
||||||
|
|
||||||
- name: Build project wiki
|
- name: Build project wiki
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
working-directory: docs/site
|
working-directory: docs/site
|
||||||
run: |
|
run: |
|
||||||
npm ci --no-fund
|
npm ci --no-fund
|
||||||
npx astro build
|
npx astro build
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Pages
|
- name: Deploy to Cloudflare Pages
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
working-directory: docs/site
|
working-directory: docs/site
|
||||||
env:
|
env:
|
||||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
@@ -144,6 +249,7 @@ jobs:
|
|||||||
--commit-dirty=true
|
--commit-dirty=true
|
||||||
|
|
||||||
- name: Commit generated docs
|
- name: Commit generated docs
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
env:
|
env:
|
||||||
BOT_GPG_KEY: ${{ secrets.CI_GPG_PRIVATE_KEY }}
|
BOT_GPG_KEY: ${{ secrets.CI_GPG_PRIVATE_KEY }}
|
||||||
run: |
|
run: |
|
||||||
@@ -171,7 +277,25 @@ jobs:
|
|||||||
git add docs/fish-config.md docs/fish-config.1 conf.d/__fish_config_op_registry.fish
|
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 diff --cached --quiet && echo "No changes to commit" && exit 0
|
||||||
git commit -m "chore(docs): regenerate manual, man page, and component registry"
|
git commit -m "chore(docs): regenerate manual, man page, and component registry"
|
||||||
git push
|
|
||||||
|
# npm ci + astro build + the Cloudflare deploy above can take
|
||||||
|
# several minutes, so main can move (another PR merges) before
|
||||||
|
# this push lands -- a bare `git push` was seen rejected as
|
||||||
|
# non-fast-forward for exactly that reason (run 976). This
|
||||||
|
# commit only ever touches generated files, so a rebase onto
|
||||||
|
# whatever landed is always mechanical; retry it a few times
|
||||||
|
# against a live race instead of failing the whole job.
|
||||||
|
pushed=0
|
||||||
|
for attempt in 1 2 3; do
|
||||||
|
if git push; then
|
||||||
|
pushed=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "push rejected (attempt $attempt/3), rebasing onto origin/main..." >&2
|
||||||
|
git fetch origin main
|
||||||
|
git rebase origin/main
|
||||||
|
done
|
||||||
|
test "$pushed" -eq 1
|
||||||
|
|
||||||
# Stand-in for the GitHub mirror so the commit gets a completed status
|
# Stand-in for the GitHub mirror so the commit gets a completed status
|
||||||
# instead of the real jobs above sitting queued forever for a
|
# instead of the real jobs above sitting queued forever for a
|
||||||
@@ -183,4 +307,4 @@ jobs:
|
|||||||
- name: Note that CI runs on Gitea
|
- name: Note that CI runs on Gitea
|
||||||
run: |
|
run: |
|
||||||
echo "This repository mirrors from Gitea (git.rootiest.dev), where CI actually runs."
|
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."
|
echo "See the commit's status on the Gitea instance for the real test/docs results."
|
||||||
|
|||||||
+71
-6
@@ -170,6 +170,33 @@ assumes you *do* have push access (maintainers, regular contributors).
|
|||||||
approval before doing this** — there's no standing exception, no matter
|
approval before doing this** — there's no standing exception, no matter
|
||||||
how trivial the change looks.
|
how trivial the change looks.
|
||||||
|
|
||||||
|
### Branch protection on `main`
|
||||||
|
|
||||||
|
Gitea enforces these rules server-side — not just convention, so violating
|
||||||
|
one is a hard rejection, not a review comment:
|
||||||
|
|
||||||
|
- **No direct push**, except an allowlist: the repo owner and
|
||||||
|
`fishconfig-bot` (the docs-regen commit CI makes on every push — see
|
||||||
|
[Documentation Pipeline](#documentation-pipeline)). Everyone else merges
|
||||||
|
through a PR, which is what [the bullets above](#branching--pull-requests)
|
||||||
|
already describe.
|
||||||
|
- **Force-push is disabled outright** — no allowlist exception.
|
||||||
|
- **Signed commits are required.** An unsigned or bad-signature commit is
|
||||||
|
rejected at push time, which is what the tracked `pre-push` hook in
|
||||||
|
[Getting Started](#getting-started) catches locally before you find out
|
||||||
|
the hard way from Gitea.
|
||||||
|
- **`CI / test (pull_request)` must pass** before a PR can merge.
|
||||||
|
- **A PR with changes requested by a reviewer can't merge** until that's
|
||||||
|
resolved, even with enough approvals otherwise.
|
||||||
|
- **A PR behind `main` can't merge** — use Gitea's "Update branch" first.
|
||||||
|
This bites a stacked PR specifically: once the PR it was based on merges,
|
||||||
|
the stacked one now shows behind `main` and needs an update before it can
|
||||||
|
merge too, even though nothing about its own content changed.
|
||||||
|
- The repo owner is on the bypass allowlist and can override the above when
|
||||||
|
there's a genuine reason to (the emergency valve for [item 3 of keeping
|
||||||
|
feature branches focused](#branching--pull-requests)) — this is the
|
||||||
|
exception, not a way to routinely skip review.
|
||||||
|
|
||||||
### Pull request descriptions
|
### Pull request descriptions
|
||||||
|
|
||||||
Fill in `.github/PULL_REQUEST_TEMPLATE.md` — Gitea pre-loads it into the
|
Fill in `.github/PULL_REQUEST_TEMPLATE.md` — Gitea pre-loads it into the
|
||||||
@@ -390,8 +417,8 @@ all optional except where noted:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `CATEGORY` | **Required to appear in the manual at all** — see below. |
|
| `CATEGORY` | **Required to appear in the manual at all** — see below. |
|
||||||
| `COMPONENT` | Only for functions gated by the [opinionated-component system](#opinionated-components). |
|
| `COMPONENT` | Only for functions gated by the [opinionated-component system](#opinionated-components). |
|
||||||
| `DEPENDENCIES` | Other functions this one calls that a reader may want to look up. |
|
| `DEPENDENCIES` | Other functions, and external CLI tools, this one needs for full functionality — required or optional-with-fallback alike. |
|
||||||
| `CLASSIFICATION` | Hazard/shadow-interaction tags — see below. |
|
| `CLASSIFICATION` | Hazard/shadow-interaction tags, plus a couple of general-purpose ones (`manual-section`) — see below. |
|
||||||
| `SYNOPSIS` | One-line usage form. |
|
| `SYNOPSIS` | One-line usage form. |
|
||||||
| `DESCRIPTION` | Prose description; can span multiple paragraphs. |
|
| `DESCRIPTION` | Prose description; can span multiple paragraphs. |
|
||||||
| `ARGUMENTS` | Flags/positional args, one per line. |
|
| `ARGUMENTS` | Flags/positional args, one per line. |
|
||||||
@@ -447,13 +474,51 @@ If your function genuinely doesn't fit any of these, add a new
|
|||||||
`docs/manual/05-functions/NN-your-category.md` stub (with frontmatter
|
`docs/manual/05-functions/NN-your-category.md` stub (with frontmatter
|
||||||
matching its siblings) rather than force-fitting it into an existing one.
|
matching its siblings) rather than force-fitting it into an existing one.
|
||||||
|
|
||||||
**`CLASSIFICATION` flags hazards and shadow interactions, optional and
|
**`CLASSIFICATION` is the general-purpose tag field, optional and omitted
|
||||||
omitted when nothing applies:** whether the function calls a
|
when nothing applies:** mostly hazards and shadow interactions — whether
|
||||||
|
the function calls a
|
||||||
[C1-shadowed command](docs/manual/08-components-reference/01-c1-command-shadows.md)
|
[C1-shadowed command](docs/manual/08-components-reference/01-c1-command-shadows.md)
|
||||||
bare wanting the override (`uses-shadow(ls)`) or bypasses it deliberately
|
bare wanting the override (`uses-shadow(ls)`) or bypasses it deliberately
|
||||||
via `command`/`builtin` (`bypasses-shadow(cat)`), and general hazards —
|
via `command`/`builtin` (`bypasses-shadow(cat)`), and general hazards —
|
||||||
`destructive`, `network`, `blocking-prompt`. Full tag definitions and
|
`destructive`, `network`, `blocking-prompt` — but not exclusively: a
|
||||||
placement rule: [`docs/function-classification-schema.md`](docs/function-classification-schema.md).
|
function with its own dedicated manual section (below) carries
|
||||||
|
`manual-section(<slug>)` here too, so that fact is grep-able without
|
||||||
|
reading every `NOTES` field. Full tag definitions and placement rule:
|
||||||
|
[`docs/function-classification-schema.md`](docs/function-classification-schema.md).
|
||||||
|
|
||||||
|
### Dedicated manual sections for complex subsystems
|
||||||
|
|
||||||
|
A doc-header's `DESCRIPTION` is for that one function's usage — it stops
|
||||||
|
being the right place once a subsystem spans several functions, has its
|
||||||
|
own file layout, or has enough behavior (a decision table, a safety
|
||||||
|
model) that cramming it into one function's header would make that
|
||||||
|
header useless as a quick reference. When that happens, give the
|
||||||
|
subsystem its own numbered top-level section under `docs/manual/`
|
||||||
|
(follow the sibling sections' frontmatter shape: `title`, `manTitle`,
|
||||||
|
`sidebar.order`, `helpKeywords`) instead of stretching the header.
|
||||||
|
`docs/manual/16-agent-tooling.md` (`agents-init`/`agents-vault`/the
|
||||||
|
`AGENTS/` sub-repository) is the existing example — its own doc-headers
|
||||||
|
stay short and point there for the full picture, the same way this
|
||||||
|
document points at other reference files rather than repeating them.
|
||||||
|
|
||||||
|
A function with a dedicated section carries `manual-section(<slug>)` in
|
||||||
|
its own `# CLASSIFICATION` (see `functions/agents-init.fish`; full tag
|
||||||
|
definition in
|
||||||
|
[`docs/function-classification-schema.md`](docs/function-classification-schema.md))
|
||||||
|
— that's what makes the section discoverable without reading every
|
||||||
|
function's `NOTES` by hand, and it's what `docs/build-manual.py` reads to
|
||||||
|
render the "See also" line on the function's generated entry.
|
||||||
|
`docs/verify-manual.py` fails the build if the slug doesn't resolve to a
|
||||||
|
real page, so a typo or a renamed file can't go unnoticed — but it cannot
|
||||||
|
check the *content* is current. A `# NOTES` line pointing at the same page
|
||||||
|
(see the existing example) is worth adding too, for a reader who only
|
||||||
|
reads the header text rather than the generated docs, but the tag is the
|
||||||
|
part something else actually verifies.
|
||||||
|
|
||||||
|
This is a genuine exception to "the doc-header is the single source of
|
||||||
|
truth" above. **Whenever you change what one of these functions does,
|
||||||
|
update its dedicated section in the same commit or pull request** — not
|
||||||
|
as a follow-up.
|
||||||
|
|
||||||
### Private/internal helper functions
|
### Private/internal helper functions
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
# Fish Shell Configuration
|
# Rootiest Fish Configuration
|
||||||
|
|
||||||
A feature-rich Fish shell configuration for CachyOS (Arch Linux),
|
This isn't a generic Fish shell configuration — it's the Rootiest Fish
|
||||||
built around a Catppuccin Mocha aesthetic with a curated set of modern
|
Configuration: a feature-rich setup for CachyOS (Arch Linux), built
|
||||||
CLI tool integrations, smart shell functions, and a heavily customized
|
around a Catppuccin Mocha aesthetic with a curated set of modern CLI
|
||||||
|
tool integrations, smart shell functions, and a heavily customized
|
||||||
abbreviation system for keyboard-driven workflows.
|
abbreviation system for keyboard-driven workflows.
|
||||||
|
|
||||||
📖 **[Project Wiki](https://fish.rootiest.fyi/)**
|
📖 **[Project Wiki](https://fish.rootiest.fyi/)**
|
||||||
@@ -132,7 +133,7 @@ silent until you enable logging.
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
### [📖 Documentation Site](https://fish.rootiest.fyi/)
|
### [📖 Documentation Wiki](https://fish.rootiest.fyi/)
|
||||||
|
|
||||||
A Starlight-powered site rebuilt on every push to `main`. It covers
|
A Starlight-powered site rebuilt on every push to `main`. It covers
|
||||||
configuration variables, key bindings, abbreviations, all functions, the
|
configuration variables, key bindings, abbreviations, all functions, the
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
complete --command fisher --exclusive --long help --description "Print help"
|
|
||||||
complete --command fisher --exclusive --long version --description "Print version"
|
|
||||||
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments install --description "Install plugins"
|
|
||||||
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments update --description "Update installed plugins"
|
|
||||||
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments remove --description "Remove installed plugins"
|
|
||||||
complete --command fisher --exclusive --condition __fish_use_subcommand --arguments list --description "List installed plugins matching regex"
|
|
||||||
complete --command fisher --exclusive --condition "__fish_seen_subcommand_from update remove" --arguments "(fisher list)"
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# ╭──────────────────────────────────────────────────────────╮
|
||||||
|
# │ GitIgnore Boilerplate Template │
|
||||||
|
# ╰──────────────────────────────────────────────────────────╯
|
||||||
|
#
|
||||||
|
# ──────────────────── OS-Generated Files ────────────────────
|
||||||
|
# automatic backup files created by some editors (e.g., Vim, Emacs)
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files created if a process still has a handle to a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# MacOS junk
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# files created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
# ─────────────────── Debug/Temporary/Testing ────────────────
|
||||||
|
# Matches OLD / .OLD
|
||||||
|
[Oo][Ll][Dd]/
|
||||||
|
.[Oo][Ll][Dd]/
|
||||||
|
|
||||||
|
# Matches DISABLE / .DISABLE
|
||||||
|
[Dd][Ii][Ss][Aa][Bb][Ll][Ee]/
|
||||||
|
.[Dd][Ii][Ss][Aa][Bb][Ll][Ee]/
|
||||||
|
|
||||||
|
# Matches DISABLED / .DISABLED
|
||||||
|
[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd]/
|
||||||
|
.[Dd][Ii][Ss][Aa][Bb][Ll][Ee][Dd]/
|
||||||
|
|
||||||
|
# Matches DEBUG / .DEBUG
|
||||||
|
[Dd][Ee][Bb][Uu][Gg]/
|
||||||
|
.[Dd][Ee][Bb][Uu][Gg]/
|
||||||
|
|
||||||
|
# Matches TMP / .TMP
|
||||||
|
[Tt][Mm][Pp]/
|
||||||
|
.[Tt][Mm][Pp]/
|
||||||
|
|
||||||
|
# Matches TEMP / .TEMP
|
||||||
|
[Tt][Ee][Mm][Pp]/
|
||||||
|
.[Tt][Ee][Mm][Pp]/
|
||||||
|
|
||||||
|
# Matches TEMPORARY / .TEMPORARY
|
||||||
|
[Tt][Ee][Mm][Pp][Oo][Rr][Aa][Rr][Yy]/
|
||||||
|
.[Tt][Ee][Mm][Pp][Oo][Rr][Aa][Rr][Yy]/
|
||||||
|
|
||||||
|
# Matches TESTING / .TESTING
|
||||||
|
[Tt][Ee][Ss][Tt][Ii][Nn][Gg]/
|
||||||
|
.[Tt][Ee][Ss][Tt][Ii][Nn][Gg]/
|
||||||
|
|
||||||
|
# ─────────────────── Scratchpad / Scratch Files ───────────────
|
||||||
|
# Root-only files/folders starting with scratch or .scratch
|
||||||
|
/[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*
|
||||||
|
/.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*
|
||||||
|
|
||||||
|
# Matches SCRATCH / .SCRATCH anywhere (directory)
|
||||||
|
[Ss][Cc][Rr][Aa][Tt][Cc][Hh]/
|
||||||
|
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]/
|
||||||
|
|
||||||
|
# Matches SCRATCHPAD / .SCRATCHPAD anywhere (file or directory)
|
||||||
|
[Ss][Cc][Rr][Aa][Tt][Cc][Hh][Pp][Aa][Dd]
|
||||||
|
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh][Pp][Aa][Dd]
|
||||||
|
|
||||||
|
# Matches any directory starting with .SCRATCH anywhere
|
||||||
|
.[Ss][Cc][Rr][Aa][Tt][Cc][Hh]*/
|
||||||
|
|
||||||
|
# ─────────────────── Dev Notes / Working Notes ────────────────
|
||||||
|
# Root-only files/folders starting with devnote or .devnote
|
||||||
|
/[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*
|
||||||
|
/.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*
|
||||||
|
|
||||||
|
# Matches DEVNOTE / .DEVNOTE anywhere (file or directory)
|
||||||
|
[Dd][Ee][Vv][Nn][Oo][Tt][Ee]
|
||||||
|
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]
|
||||||
|
|
||||||
|
# Matches DEVNOTES / .DEVNOTES anywhere (file or directory)
|
||||||
|
[Dd][Ee][Vv][Nn][Oo][Tt][Ee][Ss]
|
||||||
|
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee][Ss]
|
||||||
|
|
||||||
|
# Matches any directory starting with .DEVNOTE anywhere
|
||||||
|
.[Dd][Ee][Vv][Nn][Oo][Tt][Ee]*/
|
||||||
|
|
||||||
|
# ─────────────────── AI Sessions and Rules ──────────────────
|
||||||
|
# CLAUDE.md / AGENTS.md / GEMINI.md / ANTIGRAVITY.md are deliberately NOT
|
||||||
|
# ignored here: an increasing number of projects commit these instruction
|
||||||
|
# files on purpose. Only the tool-owned session/state dirs are ignored.
|
||||||
|
# Matches .claude* anywhere (files or directories)
|
||||||
|
.[Cc][Ll][Aa][Uu][Dd][Ee]*
|
||||||
|
|
||||||
|
# Matches .gemini* anywhere (files or directories)
|
||||||
|
.[Gg][Ee][Mm][Ii][Nn][Ii]*
|
||||||
|
|
||||||
|
# Matches .antigravity* anywhere (files or directories)
|
||||||
|
.[Aa][Nn][Tt][Ii][Gg][Rr][Aa][Vv][Ii][Tt][Yy]*
|
||||||
|
|
||||||
|
# Matches .AGY* anywhere (files or directories)
|
||||||
|
.[Aa][Gg][Yy]*
|
||||||
|
|
||||||
|
# Matches .agents* anywhere (files or directories)
|
||||||
|
.[Aa][Gg][Ee][Nn][Tt][Ss]*
|
||||||
|
|
||||||
|
# Matches .REMEMBER* anywhere (files or directories)
|
||||||
|
.[Rr][Ee][Mm][Ee][Mm][Bb][Ee][Rr]*
|
||||||
|
|
||||||
|
# ──────────────────── Planning Artifacts ───────────────────
|
||||||
|
# Catalog files generated by pre-implementation analysis passes
|
||||||
|
.superpowers
|
||||||
|
docs/superpowers
|
||||||
|
docs/specs
|
||||||
|
docs/devlogs
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────────────────────────
|
||||||
+66
-8
@@ -202,7 +202,7 @@ def build_concat(root: Path) -> str:
|
|||||||
Only bodies are passed: the pandoc metadata block above is not prose
|
Only bodies are passed: the pandoc metadata block above is not prose
|
||||||
and must survive byte-for-byte.
|
and must survive byte-for-byte.
|
||||||
"""
|
"""
|
||||||
entries = build_entries(mt.parse_functions(FUNCTIONS))
|
entries = build_entries(mt.parse_functions(FUNCTIONS), root=root)
|
||||||
chunks: list[str] = []
|
chunks: list[str] = []
|
||||||
pandoc_path = root / "_pandoc.yml"
|
pandoc_path = root / "_pandoc.yml"
|
||||||
if pandoc_path.exists():
|
if pandoc_path.exists():
|
||||||
@@ -726,7 +726,44 @@ def _classification_tags(raw: list[str]) -> list[str]:
|
|||||||
return [t for t in tags if t]
|
return [t for t in tags if t]
|
||||||
|
|
||||||
|
|
||||||
def render_entry(fn: dict[str, list[str]], used_by: list[str], link=None) -> str:
|
MANUAL_SECTION_RE = re.compile(r"^manual-section\(([\w./-]+)\)$")
|
||||||
|
|
||||||
|
|
||||||
|
def _manual_section_slug(tags: list[str]) -> str | None:
|
||||||
|
"""Pull the slug out of a `manual-section(<slug>)` CLASSIFICATION tag, if present."""
|
||||||
|
for tag in tags:
|
||||||
|
m = MANUAL_SECTION_RE.match(tag)
|
||||||
|
if m:
|
||||||
|
return m.group(1)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_manual_section(root: Path | None, slug: str) -> tuple[str, str, str] | None:
|
||||||
|
"""Resolve a manual-section(<slug>) tag to (display label, site link, doc-relative path).
|
||||||
|
|
||||||
|
Looks for <slug>.md (a top-level single-file section) or <slug>/index.md
|
||||||
|
(a directory-based section), matching the two shapes docs/manual/
|
||||||
|
actually uses. The display label is read fresh from the target's own
|
||||||
|
frontmatter (manTitle, falling back to title) rather than duplicated in
|
||||||
|
the tag, so a renumbered section never needs its tag updated -- only
|
||||||
|
the slug (the filename) does, and that only changes if the page itself
|
||||||
|
is renamed. Returns None -- silently, this is a build, not a check;
|
||||||
|
verify-manual.py is where a dangling slug is a real failure -- when
|
||||||
|
<root> is unset or neither candidate exists.
|
||||||
|
"""
|
||||||
|
if root is None:
|
||||||
|
return None
|
||||||
|
for relpath in (f"{slug}.md", f"{slug}/index.md"):
|
||||||
|
if (root / relpath).exists():
|
||||||
|
fm, _ = mt.parse(root / relpath)
|
||||||
|
label = fm.get("manTitle") or fm.get("title", slug)
|
||||||
|
return label, f"/{slug}/", relpath
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def render_entry(
|
||||||
|
fn: dict[str, list[str]], used_by: list[str], link=None, root: Path | None = None
|
||||||
|
) -> str:
|
||||||
"""Render one parsed function header as a manual entry body.
|
"""Render one parsed function header as a manual entry body.
|
||||||
|
|
||||||
Emits the same man-page shape Section 5 was authored in — one 4-space
|
Emits the same man-page shape Section 5 was authored in — one 4-space
|
||||||
@@ -759,15 +796,22 @@ def render_entry(fn: dict[str, list[str]], used_by: list[str], link=None) -> str
|
|||||||
def names(raw: list[str]) -> list[str]:
|
def names(raw: list[str]) -> list[str]:
|
||||||
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
|
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
|
||||||
|
|
||||||
|
classification = _classification_tags(fn.get("CLASSIFICATION", []))
|
||||||
refs = []
|
refs = []
|
||||||
for label, values in (
|
for label, values in (
|
||||||
("Dependencies", names(fn.get("DEPENDENCIES", []))),
|
("Dependencies", names(fn.get("DEPENDENCIES", []))),
|
||||||
("Classification", _classification_tags(fn.get("CLASSIFICATION", []))),
|
("Classification", classification),
|
||||||
("Used by", sorted(used_by)),
|
("Used by", sorted(used_by)),
|
||||||
):
|
):
|
||||||
if values:
|
if values:
|
||||||
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
|
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
|
||||||
refs.append(f"**{label}:** {rendered}")
|
refs.append(f"**{label}:** {rendered}")
|
||||||
|
slug = _manual_section_slug(classification)
|
||||||
|
if slug:
|
||||||
|
resolved = _resolve_manual_section(root, slug)
|
||||||
|
if resolved:
|
||||||
|
label, _href, relpath = resolved
|
||||||
|
refs.append(f"**See also:** {label} (`docs/manual/{relpath}`)")
|
||||||
if refs:
|
if refs:
|
||||||
block += "\n\n" + "\n\n".join(refs)
|
block += "\n\n" + "\n\n".join(refs)
|
||||||
return block
|
return block
|
||||||
@@ -871,7 +915,9 @@ SITE_SECTIONS = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def render_entry_site(fn: dict[str, list[str]], used_by: list[str], link=None) -> str:
|
def render_entry_site(
|
||||||
|
fn: dict[str, list[str]], used_by: list[str], link=None, root: Path | None = None
|
||||||
|
) -> str:
|
||||||
"""Render one parsed function header as a manual entry body for the site.
|
"""Render one parsed function header as a manual entry body for the site.
|
||||||
|
|
||||||
Unlike `render_entry` (the single indented man-page block pandoc wants,
|
Unlike `render_entry` (the single indented man-page block pandoc wants,
|
||||||
@@ -905,15 +951,22 @@ def render_entry_site(fn: dict[str, list[str]], used_by: list[str], link=None) -
|
|||||||
def names(raw: list[str]) -> list[str]:
|
def names(raw: list[str]) -> list[str]:
|
||||||
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
|
return [n for n in re.split(r"[,\s]+", " ".join(raw)) if n]
|
||||||
|
|
||||||
|
classification = _classification_tags(fn.get("CLASSIFICATION", []))
|
||||||
refs = []
|
refs = []
|
||||||
for label, values in (
|
for label, values in (
|
||||||
("Dependencies", names(fn.get("DEPENDENCIES", []))),
|
("Dependencies", names(fn.get("DEPENDENCIES", []))),
|
||||||
("Classification", _classification_tags(fn.get("CLASSIFICATION", []))),
|
("Classification", classification),
|
||||||
("Used by", sorted(used_by)),
|
("Used by", sorted(used_by)),
|
||||||
):
|
):
|
||||||
if values:
|
if values:
|
||||||
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
|
rendered = ", ".join(link(v) if link else f"`{v}`" for v in values)
|
||||||
refs.append(f"**{label}:** {rendered}")
|
refs.append(f"**{label}:** {rendered}")
|
||||||
|
slug = _manual_section_slug(classification)
|
||||||
|
if slug:
|
||||||
|
resolved = _resolve_manual_section(root, slug)
|
||||||
|
if resolved:
|
||||||
|
label, href, _relpath = resolved
|
||||||
|
refs.append(f"**See also:** [{label}]({href})")
|
||||||
if refs:
|
if refs:
|
||||||
parts.append("\n\n".join(refs))
|
parts.append("\n\n".join(refs))
|
||||||
|
|
||||||
@@ -921,7 +974,7 @@ def render_entry_site(fn: dict[str, list[str]], used_by: list[str], link=None) -
|
|||||||
|
|
||||||
|
|
||||||
def build_entries(
|
def build_entries(
|
||||||
functions: dict[str, dict], link=None, site: bool = False
|
functions: dict[str, dict], link=None, site: bool = False, root: Path | None = None
|
||||||
) -> dict[str, list[tuple[str, str]]]:
|
) -> dict[str, list[tuple[str, str]]]:
|
||||||
"""Group rendered entries by category stem, ordered by function name.
|
"""Group rendered entries by category stem, ordered by function name.
|
||||||
|
|
||||||
@@ -929,6 +982,9 @@ def build_entries(
|
|||||||
authored: a bidirectional link maintained by hand drifts the moment one
|
authored: a bidirectional link maintained by hand drifts the moment one
|
||||||
side is edited. `site` selects `render_entry_site` (headings + tables)
|
side is edited. `site` selects `render_entry_site` (headings + tables)
|
||||||
over `render_entry` (the man-page indented block `build_concat` needs).
|
over `render_entry` (the man-page indented block `build_concat` needs).
|
||||||
|
`root` (docs/manual/) resolves any `manual-section(<slug>)`
|
||||||
|
CLASSIFICATION tag to that page's own title -- omitted, the default,
|
||||||
|
an entry with the tag just gets no "See also" line rather than failing.
|
||||||
"""
|
"""
|
||||||
used_by: dict[str, list[str]] = {}
|
used_by: dict[str, list[str]] = {}
|
||||||
for name, fn in functions.items():
|
for name, fn in functions.items():
|
||||||
@@ -940,7 +996,7 @@ def build_entries(
|
|||||||
out: dict[str, list[tuple[str, str]]] = {}
|
out: dict[str, list[tuple[str, str]]] = {}
|
||||||
for name in sorted(functions):
|
for name in sorted(functions):
|
||||||
fn = functions[name]
|
fn = functions[name]
|
||||||
body = render(fn, used_by.get(name, []), link)
|
body = render(fn, used_by.get(name, []), link, root=root)
|
||||||
out.setdefault(fn["CATEGORY"][0], []).append((name, body))
|
out.setdefault(fn["CATEGORY"][0], []).append((name, body))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -1041,7 +1097,9 @@ def build_site(root: Path, out: Path) -> list[dict]:
|
|||||||
out.mkdir(parents=True)
|
out.mkdir(parents=True)
|
||||||
|
|
||||||
functions = mt.parse_functions(FUNCTIONS)
|
functions = mt.parse_functions(FUNCTIONS)
|
||||||
entries = build_entries(functions, link=lambda n: _entry_link(n, functions), site=True)
|
entries = build_entries(
|
||||||
|
functions, link=lambda n: _entry_link(n, functions), site=True, root=root
|
||||||
|
)
|
||||||
|
|
||||||
sidebar: list[dict] = [{"label": "Home", "link": "/"}]
|
sidebar: list[dict] = [{"label": "Home", "link": "/"}]
|
||||||
standard_groups: dict = {}
|
standard_groups: dict = {}
|
||||||
|
|||||||
+681
-38
File diff suppressed because it is too large
Load Diff
+15
-8
@@ -423,14 +423,21 @@ contributing=# 15. CONTRIBUTING
|
|||||||
contribute=# 15. CONTRIBUTING
|
contribute=# 15. CONTRIBUTING
|
||||||
forge=# 15. CONTRIBUTING
|
forge=# 15. CONTRIBUTING
|
||||||
|
|
||||||
# ── Section 16: Attribution ───────────────────────────────────
|
# ── Section 16: AI Agent Tooling ──────────────────────────────
|
||||||
attribution=# 16. ATTRIBUTION
|
agent=# 16. AI AGENT TOOLING
|
||||||
credits=# 16. ATTRIBUTION
|
agent-tooling=# 16. AI AGENT TOOLING
|
||||||
|
agents.md=# 16. AI AGENT TOOLING
|
||||||
|
claude-code=# 16. AI AGENT TOOLING
|
||||||
|
antigravity=# 16. AI AGENT TOOLING
|
||||||
|
|
||||||
# ── Section 17: License ───────────────────────────────────────
|
# ── Section 17: Attribution ───────────────────────────────────
|
||||||
license=# 17. LICENSE
|
attribution=# 17. ATTRIBUTION
|
||||||
licensing=# 17. LICENSE
|
credits=# 17. ATTRIBUTION
|
||||||
agpl=# 17. LICENSE
|
|
||||||
copyright=# 17. LICENSE
|
# ── Section 18: License ────────────────────────────────────────
|
||||||
|
license=# 18. LICENSE
|
||||||
|
licensing=# 18. LICENSE
|
||||||
|
agpl=# 18. LICENSE
|
||||||
|
copyright=# 18. LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+608
-35
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,14 @@ doc-header label. It's referenced from code comments and commit messages —
|
|||||||
link here, not to anything under `AGENTS/` (that tree is git-ignored local
|
link here, not to anything under `AGENTS/` (that tree is git-ignored local
|
||||||
agent state, not part of the repo).
|
agent state, not part of the repo).
|
||||||
|
|
||||||
|
`CLASSIFICATION` isn't limited to command-safety hazards, even though most
|
||||||
|
of the closed set below is exactly that — it's the general-purpose place to
|
||||||
|
tag what a function touches or how it behaves, whenever that's worth
|
||||||
|
surfacing without reading the function's own body. `manual-section(<slug>)`
|
||||||
|
is the one tag in the set that isn't a hazard at all: it marks a function
|
||||||
|
that has its own dedicated manual section beyond this header (see
|
||||||
|
[Dedicated manual sections for complex subsystems](../CONTRIBUTING.md#dedicated-manual-sections-for-complex-subsystems)).
|
||||||
|
|
||||||
See [Public function documentation header](../CONTRIBUTING.md#public-function-documentation-header)
|
See [Public function documentation header](../CONTRIBUTING.md#public-function-documentation-header)
|
||||||
in `CONTRIBUTING.md` for where `CLASSIFICATION` fits among the other header
|
in `CONTRIBUTING.md` for where `CLASSIFICATION` fits among the other header
|
||||||
labels, and [C1 — Command Shadows](manual/08-components-reference/01-c1-command-shadows.md)
|
labels, and [C1 — Command Shadows](manual/08-components-reference/01-c1-command-shadows.md)
|
||||||
@@ -72,6 +80,22 @@ it empty as a placeholder.
|
|||||||
handler, an fzf-driven picker) — the hazard this tag exists for is a
|
handler, an fzf-driven picker) — the hazard this tag exists for is a
|
||||||
script or another function calling it unexpectedly, not a human running
|
script or another function calling it unexpectedly, not a human running
|
||||||
it themselves.
|
it themselves.
|
||||||
|
- **`manual-section(<slug>)`** — this function has a dedicated manual
|
||||||
|
section beyond its own header; `<slug>` is that page's filename under
|
||||||
|
`docs/manual/` with the extension dropped (`16-agent-tooling` for
|
||||||
|
`docs/manual/16-agent-tooling.md`; a directory-based section like
|
||||||
|
`08-components-reference` uses its directory name the same way, resolved
|
||||||
|
against its `index.md`). `docs/build-manual.py` reads the target page's
|
||||||
|
own `manTitle`/`title` at build time and renders a **See also** line on
|
||||||
|
the function's generated entry — the tag only needs to keep pointing at
|
||||||
|
the right *file*; the displayed section number is never duplicated into
|
||||||
|
the tag, so it can't go stale on its own if the manual gets renumbered.
|
||||||
|
`docs/verify-manual.py` fails if the slug doesn't resolve to a real page.
|
||||||
|
Multiple functions may carry the same slug (`agents-init` and
|
||||||
|
`agents-vault` both point at `16-agent-tooling`, one section covering
|
||||||
|
both). See [Dedicated manual sections for complex subsystems](../CONTRIBUTING.md#dedicated-manual-sections-for-complex-subsystems)
|
||||||
|
in `CONTRIBUTING.md` for when a function's behavior has outgrown its
|
||||||
|
header and belongs in one of these instead.
|
||||||
|
|
||||||
## Placement
|
## Placement
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ all of these commands.
|
|||||||
grep/fgrep/egrep forced --color=auto system grep variants
|
grep/fgrep/egrep forced --color=auto system grep variants
|
||||||
dir / vdir forced --color=auto system dir / vdir
|
dir / vdir forced --color=auto system dir / vdir
|
||||||
help config intercepts "help config" → config-help fish builtin help
|
help config intercepts "help config" → config-help fish builtin help
|
||||||
claude auto-links AGENTS.md as CLAUDE.md before launch command claude
|
claude ensures AGENTS/ is scaffolded before launch command claude
|
||||||
edit multi-editor launcher (GUI/term + fallbacks) $EDITOR/nvim/nano/vi
|
edit multi-editor launcher (GUI/term + fallbacks) $EDITOR/nvim/nano/vi
|
||||||
|
|
||||||
When C1 is disabled, `rm` uses bare `command rm` with no wrapper — files
|
When C1 is disabled, `rm` uses bare `command rm` with no wrapper — files
|
||||||
@@ -64,7 +64,7 @@ and the `help config` interception.
|
|||||||
|
|
||||||
## dev-tools
|
## dev-tools
|
||||||
|
|
||||||
`claude` (AGENTS.md/CLAUDE.md auto-linking) and `edit` (multi-editor
|
`claude` (AGENTS/ scaffolding) and `edit` (multi-editor
|
||||||
launcher), plus `agy`.
|
launcher), plus `agy`.
|
||||||
|
|
||||||
## For function authors
|
## For function authors
|
||||||
|
|||||||
@@ -0,0 +1,312 @@
|
|||||||
|
---
|
||||||
|
title: AI Agent Tooling
|
||||||
|
manTitle: 16. AI AGENT TOOLING
|
||||||
|
sidebar:
|
||||||
|
order: 20
|
||||||
|
helpKeywords:
|
||||||
|
- agent
|
||||||
|
- agents-init
|
||||||
|
- agents-vault
|
||||||
|
- AGENTS.md
|
||||||
|
- claude-code
|
||||||
|
- agy
|
||||||
|
- antigravity
|
||||||
|
---
|
||||||
|
This section explains the machinery behind AI coding agents (Claude Code,
|
||||||
|
Antigravity/agy) working in a project checked out from this configuration:
|
||||||
|
where their instructions live, how they get there, and the safety rules
|
||||||
|
that keep an agent's launch-time bookkeeping from touching a repository's
|
||||||
|
own tracked history. Command-line usage for the functions named here
|
||||||
|
(`agents-init`, `agents-vault`) is generated from their own doc headers —
|
||||||
|
see Section 5.
|
||||||
|
|
||||||
|
|
||||||
|
## The problem this solves
|
||||||
|
|
||||||
|
An AI coding agent needs a persistent, project-scoped place to keep
|
||||||
|
instructions, memory, and working notes. Committing that material directly
|
||||||
|
into a project's normal history mixes two concerns that change at
|
||||||
|
different rates and for different reasons: the project's own code, and an
|
||||||
|
agent's evolving working state. It also means every project accumulates
|
||||||
|
its own copy of agent tooling (hooks, version files, convention
|
||||||
|
documents) that has nothing to do with that project's actual purpose.
|
||||||
|
|
||||||
|
`agents-init` and `agents-vault` exist to keep that material out of the
|
||||||
|
main repository while still making it feel local: an agent reads and
|
||||||
|
writes `AGENTS.md` exactly where it would expect to find it, but the real
|
||||||
|
content and its history live in a separate, self-contained git repository
|
||||||
|
that the main project never tracks.
|
||||||
|
|
||||||
|
|
||||||
|
## The AGENTS.md convention
|
||||||
|
|
||||||
|
`AGENTS.md` is a plain-text file at a project's root (and, as this
|
||||||
|
configuration extends the idea, at the root of any subdirectory with its
|
||||||
|
own scoped conventions) that an AI agent reads for repository-specific
|
||||||
|
instructions. It has become a convention shared across coding agents, not
|
||||||
|
one tool's proprietary format.
|
||||||
|
|
||||||
|
Claude Code originally required its own `CLAUDE.md` filename specifically.
|
||||||
|
It now reads `AGENTS.md` natively whenever no `CLAUDE.md` is present, which
|
||||||
|
retired the need for this configuration to create, maintain, or symlink
|
||||||
|
`CLAUDE.md` at all. A project scaffolded by `agents-init` today carries
|
||||||
|
only `AGENTS.md` — at the root, and in any subdirectory that has grown its
|
||||||
|
own scoped conventions (`functions/`, `docs/`, and so on, in this
|
||||||
|
repository's own case). A leftover `CLAUDE.md` from before this change is
|
||||||
|
retired automatically the next time `agents-init` runs: renamed, not
|
||||||
|
preserved under its old name, so nothing is ever left tracking two copies
|
||||||
|
of the same instructions under two different filenames.
|
||||||
|
|
||||||
|
|
||||||
|
## The AGENTS/ sub-repository
|
||||||
|
|
||||||
|
`agents-init` scaffolds a directory named `AGENTS/` at a project's root.
|
||||||
|
It is a self-contained git repository — its own `.git`, its own commit
|
||||||
|
history, its own hooks — and it is gitignored from the project it lives
|
||||||
|
inside. The project's own `AGENTS.md` (and every subdirectory's) is a
|
||||||
|
symlink into it:
|
||||||
|
|
||||||
|
$PROJECT/AGENTS/
|
||||||
|
├── AGENTS.md Canonical root agent spec (real file)
|
||||||
|
├── functions/AGENTS.md Canonical spec for functions/, and likewise for any other scoped subdirectory
|
||||||
|
├── plans/ Superpowers implementation plans
|
||||||
|
├── specs/ Superpowers design specs
|
||||||
|
├── devlogs/ Agent development logs
|
||||||
|
├── .version MAJOR.MINOR.PATCH structure version
|
||||||
|
└── .agents-tools/ Version-bump script and git hook shims (committed)
|
||||||
|
|
||||||
|
An agent editing `$PROJECT/AGENTS.md` is, transparently, editing
|
||||||
|
`$PROJECT/AGENTS/AGENTS.md` — the file-editing tools most agents ship with
|
||||||
|
resolve a symlinked directory's contents normally, but they cannot write
|
||||||
|
*through* a symlinked file itself, which is why the seed content
|
||||||
|
`agents-init` writes for a brand-new project spells this out directly to
|
||||||
|
the agent reading it.
|
||||||
|
|
||||||
|
IMPORTANT: This means an agent must never try to write to a *symlink
|
||||||
|
named* `AGENTS.md` directly. The seed instructions `agents-init` writes
|
||||||
|
for a fresh project tell the agent this explicitly, pointing it at the
|
||||||
|
real file inside `AGENTS/`.
|
||||||
|
|
||||||
|
### Version tracking and hooks
|
||||||
|
|
||||||
|
Every `AGENTS/` repository carries a `.version` file (seeded `1.0.0`) and
|
||||||
|
a self-contained version bumper, wired through `core.hooksPath` rather
|
||||||
|
than the ordinary `.git/hooks/` directory:
|
||||||
|
|
||||||
|
- A **pre-commit** hook bumps `.version` on every commit: the MINOR
|
||||||
|
field moves when the set of tracked top-level directories changes
|
||||||
|
(a new subdirectory convention was adopted, or one was dropped), the
|
||||||
|
PATCH field otherwise. The MAJOR field is manual-only.
|
||||||
|
- A **prepare-commit-msg** hook appends `(vX.Y.Z)` to the commit
|
||||||
|
subject, so the version history is legible from `git log` alone.
|
||||||
|
|
||||||
|
Each hook shim then chains to whatever hook of the same name the
|
||||||
|
project's *global* or *system* `core.hooksPath` already points at — a
|
||||||
|
credential scanner like ggshield, Git LFS, or anything else already
|
||||||
|
wired in ahead of this. Pointing `core.hooksPath` at `.agents-tools/hooks`
|
||||||
|
locally does not shadow those; it runs both.
|
||||||
|
|
||||||
|
The `.agents-tools/` scripts themselves are copied in from this
|
||||||
|
configuration's own `scripts/agents-tools/` and refreshed automatically
|
||||||
|
whenever their version marker moves, so every project's `AGENTS/`
|
||||||
|
repository stays current with this configuration without any manual step.
|
||||||
|
|
||||||
|
Downstream tooling that wants to know whether a project's `AGENTS/`
|
||||||
|
*structure* changed — as opposed to just its content — can read the
|
||||||
|
`.version` file's MINOR field directly rather than diffing the tree.
|
||||||
|
|
||||||
|
|
||||||
|
## Per-directory discovery
|
||||||
|
|
||||||
|
The convention is not limited to a project's root. Any directory that
|
||||||
|
carries its own `AGENTS.md` — `functions/`, `docs/`, or a subdirectory of
|
||||||
|
a much larger project with genuinely distinct conventions of its own —
|
||||||
|
gets the identical treatment: a real file inside `AGENTS/<that path>/`,
|
||||||
|
and a symlink at the project location pointing back to it. `agents-init`
|
||||||
|
finds these automatically on every run, rather than working from a fixed
|
||||||
|
list, by walking the project tree for any file literally named
|
||||||
|
`AGENTS.md` or `CLAUDE.md`.
|
||||||
|
|
||||||
|
Each directory found is settled into exactly one of four states, in
|
||||||
|
order, so a later run only ever sees a directory that is already
|
||||||
|
consistent:
|
||||||
|
|
||||||
|
1. **An inverted mirror** (an older layout, where `CLAUDE.md` was the
|
||||||
|
real file inside `AGENTS/` and `AGENTS.md` was symlinked to it) is
|
||||||
|
flipped in place — same bytes, new name.
|
||||||
|
2. **A real file at the project level, with no real file inside
|
||||||
|
`AGENTS/` yet**, is adopted: a lone `AGENTS.md` moves in as-is; a
|
||||||
|
lone `CLAUDE.md` is renamed on the way in, never preserved under its
|
||||||
|
own name. When both `AGENTS.md` and `CLAUDE.md` are real files at
|
||||||
|
once, byte-identical content is deduplicated (the `AGENTS.md` side is
|
||||||
|
kept); different content is left exactly as it is, with a warning —
|
||||||
|
this function has no way to know which one is authoritative, and
|
||||||
|
guessing wrong would silently discard the other.
|
||||||
|
3. **A stray `CLAUDE.md` inside `AGENTS/`** left over once `AGENTS.md`
|
||||||
|
is settled there is removed — nothing named `CLAUDE.md` survives
|
||||||
|
inside the mirror.
|
||||||
|
4. **The project-level symlink** is created or repaired if missing or
|
||||||
|
stale, and any `CLAUDE.md` still at the project level is removed. A
|
||||||
|
real file that turns up here *after* the mirror already settled (for
|
||||||
|
instance, an agent's own `/init`-style command writing a fresh
|
||||||
|
`CLAUDE.md`) is held to the same identical-or-differ rule as step 2:
|
||||||
|
a duplicate is dropped, anything different is left alone with a
|
||||||
|
warning rather than silently overwritten.
|
||||||
|
|
||||||
|
|
||||||
|
## Safety: what discovery will never touch
|
||||||
|
|
||||||
|
Because discovery walks the whole project tree rather than a fixed list,
|
||||||
|
it deliberately prunes several classes of directory before it ever
|
||||||
|
considers what's inside them:
|
||||||
|
|
||||||
|
- **Anything outside the project entirely.** A directory that has no
|
||||||
|
git repository of its own, but happens to carry a lone `AGENTS.md` or
|
||||||
|
`CLAUDE.md` (a home directory scaffolded this way, for instance), is
|
||||||
|
synced at that single location only — no recursive walk runs at all.
|
||||||
|
Recursive discovery only ever runs inside a real git repository.
|
||||||
|
- **Nested repositories.** Any subdirectory that is itself a git
|
||||||
|
repository — a submodule, a nested clone, a plugin checked out inside
|
||||||
|
a tool's own state directory — belongs to a different project and is
|
||||||
|
never walked into.
|
||||||
|
- **Dot-directories.** Anything named starting with `.` (`.git`,
|
||||||
|
`.claude`, `.gemini`, `.github`, and so on) is a tool's own state or
|
||||||
|
configuration, not a project's own scoped convention, and is skipped
|
||||||
|
unconditionally.
|
||||||
|
- **Generated output.** `build/`, `dist/`, `out/`, and `target/`
|
||||||
|
directories are never inspected — nothing generated by a build step
|
||||||
|
is a source of hand-authored instructions.
|
||||||
|
- **`node_modules/`**, and any directory literally named `AGENTS` other
|
||||||
|
than the current project's own mirror.
|
||||||
|
|
||||||
|
|
||||||
|
## Safety: deliberately tracked files are left alone
|
||||||
|
|
||||||
|
Discovery can reach a directory whose `AGENTS.md` or `CLAUDE.md` is
|
||||||
|
already committed to the project's own history on purpose — a team's
|
||||||
|
shared conventions file in a monorepo subdirectory, for instance, tracked
|
||||||
|
long before this configuration's owner ever cloned it. Replacing that
|
||||||
|
file with a symlink would change it from an ordinary tracked file into a
|
||||||
|
link pointing outside the repository the moment `agents-init` next runs,
|
||||||
|
which is not a decision this tool should make unattended on someone
|
||||||
|
else's behalf.
|
||||||
|
|
||||||
|
A real file is left untouched, instead of adopted or replaced, whenever
|
||||||
|
**both** of the following hold:
|
||||||
|
|
||||||
|
- it is tracked in git's index — staged or committed, checked with
|
||||||
|
`git ls-files`. A file that has never been `git add`ed is not tracked
|
||||||
|
by this definition, even if it sits right next to files that are.
|
||||||
|
- the project's `.gitignore` actually exists and has content in it.
|
||||||
|
|
||||||
|
Neither condition alone is enough to protect a file. An untracked file is
|
||||||
|
always safe to adopt, regardless of what `.gitignore` says about it
|
||||||
|
(nothing has been committed yet, so nothing is lost). A tracked file in a
|
||||||
|
project with *no* established ignore conventions at all — no
|
||||||
|
`.gitignore`, or an empty one — is treated as the very first time this
|
||||||
|
convention has been applied to that project, rather than a deliberate
|
||||||
|
choice to keep tracking it: `agents-init` adopts it the same way it would
|
||||||
|
adopt any other real file, which is the same behavior this tool has
|
||||||
|
always had for a project's own root file.
|
||||||
|
|
||||||
|
NOTE: In practice, this means a mature project with an established
|
||||||
|
`.gitignore` will have any already-committed `AGENTS.md`/`CLAUDE.md` left
|
||||||
|
alone across the board — root included — and will only ever adopt one
|
||||||
|
during that project's first encounter with this convention, before a
|
||||||
|
`.gitignore` entry for it exists yet.
|
||||||
|
|
||||||
|
When a directory is skipped for this reason, `agents-init` prints a
|
||||||
|
warning naming the file and explaining why, rather than staying silent
|
||||||
|
about a directory it chose not to touch.
|
||||||
|
|
||||||
|
|
||||||
|
## Scenario reference
|
||||||
|
|
||||||
|
Every combination of what a directory can hold, laid out directly. "No"
|
||||||
|
in the tracked column also covers a tracked file in a project with no
|
||||||
|
populated `.gitignore` (the bootstrap case, above) — both behave the same
|
||||||
|
way. Whenever the tracked column reads "Yes", that reason always wins
|
||||||
|
over the identical-or-different comparison below it, and the warning
|
||||||
|
printed names the file as tracked rather than as differing — the outcome
|
||||||
|
(left alone) is the same either way, only the explanation differs.
|
||||||
|
|
||||||
|
Settling a directory for the first time — a real `AGENTS.md`, a real
|
||||||
|
`CLAUDE.md`, both, or neither, discovered fresh:
|
||||||
|
|
||||||
|
Found Deliberately tracked? Result
|
||||||
|
-------------------------------- --------------------- -----------------------------------------------
|
||||||
|
Only AGENTS.md (real) No Adopted into AGENTS/, symlinked back.
|
||||||
|
Only AGENTS.md (real) Yes Left exactly as it is; not adopted.
|
||||||
|
Only CLAUDE.md (real) No Adopted, renamed to AGENTS.md, symlinked back.
|
||||||
|
Only CLAUDE.md (real) Yes Left exactly as it is; not adopted or renamed.
|
||||||
|
Both, byte-identical No AGENTS.md adopted; duplicate CLAUDE.md dropped.
|
||||||
|
Both, byte-identical Yes (either) Left exactly as they are; neither touched.
|
||||||
|
Both, different content n/a Neither touched; warns, resolve by hand.
|
||||||
|
Mirror has CLAUDE.md (real) n/a Flipped in place: renamed, nothing lost.
|
||||||
|
Correct AGENTS.md symlink exists n/a Nothing happens -- already settled.
|
||||||
|
|
||||||
|
A new real file appearing after a directory's mirror has already settled
|
||||||
|
— an agent's own `/init`-style command, for instance, writing a fresh
|
||||||
|
`CLAUDE.md` where an `AGENTS.md` is already symlinked:
|
||||||
|
|
||||||
|
New file vs. mirror Deliberately tracked? Result
|
||||||
|
------------------- --------------------- --------------------------------------------------------
|
||||||
|
Byte-identical No Adopted as a duplicate; the new file is dropped.
|
||||||
|
Byte-identical Yes Left as it is; not adopted, even though content matches.
|
||||||
|
Different content No Left as it is; warns that it differs, resolve by hand.
|
||||||
|
Different content Yes Left as it is; warns that it's tracked, not adopted.
|
||||||
|
|
||||||
|
And whatever a directory holds, it never gets this far at all if
|
||||||
|
discovery pruned it outright — see the containment rules above: nested
|
||||||
|
repositories, dot-directories, `node_modules/`, generated-output
|
||||||
|
directories, and any directory literally named `AGENTS`.
|
||||||
|
|
||||||
|
|
||||||
|
## plans/, specs/, and devlogs/
|
||||||
|
|
||||||
|
`agents-init --plugins` (the second half of what a bare `agents-init` run
|
||||||
|
does) wires up three more directories inside `AGENTS/`: `plans/` and
|
||||||
|
`specs/` for the superpowers skills' implementation plans and design
|
||||||
|
documents, and `devlogs/` for agent-authored development notes. Real
|
||||||
|
content from every legacy location this configuration has ever used for
|
||||||
|
these (`docs/plans`, `docs/superpowers/plans`, and an older
|
||||||
|
`AGENTS/plugins/` layer from before the sub-repository consolidated them)
|
||||||
|
is merged into the canonical `AGENTS/plans` and `AGENTS/specs` on first
|
||||||
|
run, and the legacy locations are removed once merged.
|
||||||
|
|
||||||
|
`docs/superpowers/plans` and `docs/superpowers/specs` are always
|
||||||
|
symlinked to their `AGENTS/` counterparts, because the superpowers skills
|
||||||
|
expect to find them there by default. `docs/plans`, `docs/specs`, and
|
||||||
|
`docs/devlogs` are only created as symlinks when a project already had a
|
||||||
|
real directory by that name — nothing forces those paths to exist for a
|
||||||
|
project that never used them.
|
||||||
|
|
||||||
|
|
||||||
|
## The launch lifecycle
|
||||||
|
|
||||||
|
The `claude` and `agy` wrapper functions each run `agents-init --quiet`
|
||||||
|
(full setup: both the `AGENTS.md` symlink step and the plans/specs/devlogs
|
||||||
|
wiring) before launching the real CLI, on every invocation. This is what
|
||||||
|
makes the whole system self-healing: a project that has drifted from the
|
||||||
|
expected layout — a stale symlink, a newly-added subdirectory's
|
||||||
|
instructions not yet adopted, a leftover `CLAUDE.md` — is corrected
|
||||||
|
automatically the next time an agent is launched there, with no separate
|
||||||
|
setup step for a person to remember.
|
||||||
|
|
||||||
|
At the end of every `agents-init` run, any uncommitted change inside
|
||||||
|
`AGENTS/` is committed automatically, so whatever an agent wrote during
|
||||||
|
its session is captured without anyone needing to run `git add` on a
|
||||||
|
repository they were never meant to think about directly. That commit is
|
||||||
|
strictly local: `agents-init` never fetches or pushes, because a network
|
||||||
|
round trip running synchronously ahead of every agent launch would block
|
||||||
|
the launch itself for as long as an unreachable remote takes to time out.
|
||||||
|
A project's `AGENTS/` repository that has its own upstream is pulled and
|
||||||
|
pushed by hand, on its owner's own schedule.
|
||||||
|
|
||||||
|
`agents-vault` is a related but distinct tool: where `AGENTS/` holds
|
||||||
|
*one project's* agent state, `agents-vault` backs up curated agent memory
|
||||||
|
that lives *outside* any project tree entirely — `~/.claude/projects/*/memory`
|
||||||
|
and similar host-scoped locations — into its own host-scoped repository.
|
||||||
|
The `claude`/`agy` wrappers sync both on every launch. See
|
||||||
|
`__fish_agent_vault_autopush` in Section 7 for its one user-facing
|
||||||
|
configuration variable; command-line usage for both tools is in Section 5.
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: Attribution
|
title: Attribution
|
||||||
manTitle: 16. ATTRIBUTION
|
manTitle: 17. ATTRIBUTION
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 20
|
order: 21
|
||||||
helpKeywords:
|
helpKeywords:
|
||||||
- attribution
|
- attribution
|
||||||
- credits
|
- credits
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: License
|
title: License
|
||||||
manTitle: 17. LICENSE
|
manTitle: 18. LICENSE
|
||||||
sidebar:
|
sidebar:
|
||||||
order: 21
|
order: 22
|
||||||
helpKeywords:
|
helpKeywords:
|
||||||
- license
|
- license
|
||||||
- licensing
|
- licensing
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Fish Shell Configuration
|
title: Rootiest Fish Configuration
|
||||||
description: Reference manual for the rootiest fish configuration.
|
description: Reference manual for the rootiest fish configuration.
|
||||||
manTitle: DESCRIPTION
|
manTitle: DESCRIPTION
|
||||||
sidebar:
|
sidebar:
|
||||||
|
|||||||
+125
-2
@@ -202,13 +202,25 @@ def test_dependencies_resolve():
|
|||||||
|
|
||||||
Catches typos, and catches stale entries when a dependency is renamed
|
Catches typos, and catches stale entries when a dependency is renamed
|
||||||
or deleted. External binaries are accepted when some file in the tree
|
or deleted. External binaries are accepted when some file in the tree
|
||||||
guards them with `type -q`, which is this repo's convention.
|
guards their availability -- `type -q name`, `command -q name`,
|
||||||
|
`command -v name`, or `which name`, this repo's four interchangeable
|
||||||
|
existence-check idioms -- either directly, or, for a multi-tool check
|
||||||
|
like dng2avif's `for cmd in magick ffmpeg avifenc exiftool; type -q
|
||||||
|
$cmd`, indirectly through a loop variable.
|
||||||
"""
|
"""
|
||||||
repo = Path(__file__).parent.parent
|
repo = Path(__file__).parent.parent
|
||||||
functions = _parsed_functions()
|
functions = _parsed_functions()
|
||||||
known = {p.stem for p in (repo / "functions").glob("*.fish")} | set(functions)
|
known = {p.stem for p in (repo / "functions").glob("*.fish")} | set(functions)
|
||||||
|
# `type` takes its own flags (e.g. `-f` to exclude functions from the
|
||||||
|
# match, as in `type -q -f $p`) that can sit between `-q` and the name
|
||||||
|
# -- skip over any of those so the guard is still recognized.
|
||||||
|
guard_re = re.compile(r"(?:type -q(?:\s+-\w+)*|command -q|command -v|which)\s+([\w.\-]+)")
|
||||||
for path in list(repo.glob("conf.d/*.fish")) + list((repo / "functions").glob("*.fish")):
|
for path in list(repo.glob("conf.d/*.fish")) + list((repo / "functions").glob("*.fish")):
|
||||||
known |= set(re.findall(r"type -q\s+(\S+)", path.read_text(encoding="utf-8")))
|
text = path.read_text(encoding="utf-8")
|
||||||
|
known |= set(guard_re.findall(text))
|
||||||
|
for var, names in re.findall(r"for\s+(\w+)\s+in\s+([^\n;]+)", text):
|
||||||
|
if re.search(rf"type -q(?:\s+-\w+)*\s+\$\{{?{re.escape(var)}\}}?\b", text):
|
||||||
|
known |= set(names.split())
|
||||||
dangling = []
|
dangling = []
|
||||||
for name, fn in functions.items():
|
for name, fn in functions.items():
|
||||||
for dep in (d for d in re.split(r"[,\s]+", " ".join(fn.get("DEPENDENCIES", []))) if d):
|
for dep in (d for d in re.split(r"[,\s]+", " ".join(fn.get("DEPENDENCIES", []))) if d):
|
||||||
@@ -1662,6 +1674,117 @@ def test_concat_section_five_stays_verbatim():
|
|||||||
assert not offenders, f"backticks inside verbatim entries: {offenders[:3]}"
|
assert not offenders, f"backticks inside verbatim entries: {offenders[:3]}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_manual_section_slug_extracts_tag():
|
||||||
|
"""`manual-section(<slug>)` is found among other CLASSIFICATION tags, or not at all."""
|
||||||
|
import build_manual
|
||||||
|
|
||||||
|
assert build_manual._manual_section_slug(["destructive", "manual-section(foo-bar)"]) == "foo-bar"
|
||||||
|
assert build_manual._manual_section_slug(["network"]) is None
|
||||||
|
assert build_manual._manual_section_slug([]) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_manual_section_reads_target_frontmatter():
|
||||||
|
"""Resolves both page shapes (top-level file, directory index) and reports None cleanly."""
|
||||||
|
import build_manual
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
root = Path(d)
|
||||||
|
(root / "solo.md").write_text(
|
||||||
|
"---\ntitle: Solo\nmanTitle: 9. SOLO\n---\nbody\n"
|
||||||
|
)
|
||||||
|
(root / "grouped").mkdir()
|
||||||
|
(root / "grouped" / "index.md").write_text(
|
||||||
|
"---\ntitle: Grouped\nmanTitle: 10. GROUPED\n---\nbody\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
label, href, relpath = build_manual._resolve_manual_section(root, "solo")
|
||||||
|
assert label == "9. SOLO", label
|
||||||
|
assert href == "/solo/", href
|
||||||
|
assert relpath == "solo.md", relpath
|
||||||
|
|
||||||
|
label, href, relpath = build_manual._resolve_manual_section(root, "grouped")
|
||||||
|
assert label == "10. GROUPED", label
|
||||||
|
assert href == "/grouped/", href
|
||||||
|
assert relpath == "grouped/index.md", relpath
|
||||||
|
|
||||||
|
assert build_manual._resolve_manual_section(root, "missing") is None
|
||||||
|
assert build_manual._resolve_manual_section(None, "solo") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_entry_see_also_appears_only_when_root_resolves():
|
||||||
|
"""The man-page See-also line needs both the tag and a root that resolves it."""
|
||||||
|
import build_manual
|
||||||
|
|
||||||
|
fn = {
|
||||||
|
"SYNOPSIS": ["thing"],
|
||||||
|
"DESCRIPTION": ["Does a thing."],
|
||||||
|
"CLASSIFICATION": ["manual-section(deep-dive)"],
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
root = Path(d)
|
||||||
|
(root / "deep-dive.md").write_text(
|
||||||
|
"---\ntitle: Deep Dive\nmanTitle: 20. DEEP DIVE\n---\nbody\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
out = build_manual.render_entry(fn, [], root=root)
|
||||||
|
assert "**See also:** 20. DEEP DIVE (`docs/manual/deep-dive.md`)" in out, out
|
||||||
|
|
||||||
|
# No root at all -- same as every other existing caller that never
|
||||||
|
# passes one -- silently omits the line rather than raising.
|
||||||
|
out_no_root = build_manual.render_entry(fn, [])
|
||||||
|
assert "See also" not in out_no_root, out_no_root
|
||||||
|
|
||||||
|
# A root that exists but doesn't have the target page: also silent.
|
||||||
|
with tempfile.TemporaryDirectory() as empty:
|
||||||
|
out_missing = build_manual.render_entry(fn, [], root=Path(empty))
|
||||||
|
assert "See also" not in out_missing, out_missing
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_entry_site_see_also_is_a_real_link():
|
||||||
|
"""The site's See-also line is a markdown link to the resolved page's site path."""
|
||||||
|
import build_manual
|
||||||
|
|
||||||
|
fn = {
|
||||||
|
"SYNOPSIS": ["thing"],
|
||||||
|
"DESCRIPTION": ["Does a thing."],
|
||||||
|
"CLASSIFICATION": ["manual-section(deep-dive)"],
|
||||||
|
}
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
root = Path(d)
|
||||||
|
(root / "deep-dive.md").write_text(
|
||||||
|
"---\ntitle: Deep Dive\nmanTitle: 20. DEEP DIVE\n---\nbody\n"
|
||||||
|
)
|
||||||
|
out = build_manual.render_entry_site(fn, [], root=root)
|
||||||
|
assert "**See also:** [20. DEEP DIVE](/deep-dive/)" in out, out
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_manual_section_tags_resolve():
|
||||||
|
"""Every manual-section(<slug>) tag on a real function points at a real page.
|
||||||
|
|
||||||
|
This is the enforcement half of the convention: build-manual.py stays
|
||||||
|
silent about a dangling slug (it just skips the See-also line), so this
|
||||||
|
is the only thing that turns a typo'd or stale slug into a failure.
|
||||||
|
"""
|
||||||
|
import build_manual
|
||||||
|
|
||||||
|
functions = mt.parse_functions(build_manual.FUNCTIONS)
|
||||||
|
checked = 0
|
||||||
|
for name, fn in functions.items():
|
||||||
|
tags = build_manual._classification_tags(fn.get("CLASSIFICATION", []))
|
||||||
|
slug = build_manual._manual_section_slug(tags)
|
||||||
|
if slug is None:
|
||||||
|
continue
|
||||||
|
checked += 1
|
||||||
|
resolved = build_manual._resolve_manual_section(build_manual.MANUAL, slug)
|
||||||
|
assert resolved is not None, (
|
||||||
|
f"{name}'s manual-section({slug}) tag doesn't resolve to "
|
||||||
|
f"docs/manual/{slug}.md or docs/manual/{slug}/index.md"
|
||||||
|
)
|
||||||
|
assert checked > 0, "expected at least one real function to carry manual-section(...)"
|
||||||
|
|
||||||
|
|
||||||
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
TESTS = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# SYNOPSIS
|
||||||
|
# _agents_init_path_is_protected <root> <path>
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
# Decides whether a real (non-symlink) file should be left alone rather
|
||||||
|
# than adopted into the AGENTS/ mirror or replaced with a symlink,
|
||||||
|
# because it looks like a deliberately tracked project file rather than
|
||||||
|
# an incidental one this project hasn't yet engaged agents-init's
|
||||||
|
# convention for.
|
||||||
|
#
|
||||||
|
# A file is protected only when BOTH are true:
|
||||||
|
# - it is tracked in git's index at <root> -- staged or committed, via
|
||||||
|
# `git ls-files`. A file that has never been `git add`ed (even if it
|
||||||
|
# sits right next to tracked files) is not tracked by this
|
||||||
|
# definition, and neither is one that is merely gitignored.
|
||||||
|
# - <root>/.gitignore exists and is non-empty -- a project with no
|
||||||
|
# ignore rules at all has never engaged with the convention this
|
||||||
|
# tool manages, so a tracked file there is more likely incidental
|
||||||
|
# (e.g. the very first agents-init run, before anyone thought to
|
||||||
|
# ignore it) than a deliberate choice to keep tracking it.
|
||||||
|
#
|
||||||
|
# Neither check alone is enough: an untracked file is always safe
|
||||||
|
# regardless of .gitignore state (nothing has been committed to protect),
|
||||||
|
# and a tracked file in a project with no established ignore
|
||||||
|
# conventions is treated as adoptable rather than deliberate.
|
||||||
|
#
|
||||||
|
# ARGUMENTS
|
||||||
|
# root Absolute path to the project root (may or may not be a git repo)
|
||||||
|
# path Absolute path to the file being considered
|
||||||
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 0 Protected -- leave this file alone
|
||||||
|
# 1 Not protected -- safe to adopt/replace
|
||||||
|
#
|
||||||
|
# EXAMPLE
|
||||||
|
# _agents_init_path_is_protected /path/to/project /path/to/project/functions/CLAUDE.md
|
||||||
|
function _agents_init_path_is_protected --argument-names root path
|
||||||
|
test -n "$root" -a -n "$path"; or return 1
|
||||||
|
git -C "$root" --literal-pathspecs ls-files --error-unmatch -- "$path" >/dev/null 2>&1; or return 1
|
||||||
|
test -s "$root/.gitignore"; or return 1
|
||||||
|
return 0
|
||||||
|
end
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _agents_init_path_is_protected
|
||||||
|
#
|
||||||
|
# CLASSIFICATION
|
||||||
|
# self-limiting(rm,mkdir), bypasses-shadow(mv)
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
# _agents_init_sync_instructions <root> <agents_dir> <rel>
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
# Normalizes one directory's agent instruction file(s) into the
|
||||||
|
# AGENTS.md-only shape: <root>/<rel>/AGENTS.md becomes a symlink to the
|
||||||
|
# real file at <agents_dir>/<rel>/AGENTS.md (or, for the root itself,
|
||||||
|
# <agents_dir>/AGENTS.md directly), and no CLAUDE.md survives anywhere
|
||||||
|
# for that directory -- neither at the project level nor inside the
|
||||||
|
# mirror.
|
||||||
|
#
|
||||||
|
# The exception is a real file that is deliberately git-tracked -- in
|
||||||
|
# git's index, in a project whose .gitignore is non-empty (see
|
||||||
|
# _agents_init_path_is_protected). Such a file is never adopted,
|
||||||
|
# relinked, or removed: whenever steps 2 or 4 find one, they leave that
|
||||||
|
# directory's instruction files exactly as they are and warn on stderr.
|
||||||
|
#
|
||||||
|
# Four states of <rel> are handled, in order, so later steps only ever
|
||||||
|
# see a settled mirror:
|
||||||
|
#
|
||||||
|
# 1. The mirror itself is inverted (CLAUDE.md real, AGENTS.md symlinked
|
||||||
|
# to it). Flipped in place: same bytes, new name.
|
||||||
|
# 2. The mirror has no real AGENTS.md yet, and the project directory
|
||||||
|
# has one or both files. If either real file is protected, both are
|
||||||
|
# left untouched, the mirror is not populated, and a warning naming
|
||||||
|
# the protected file(s) goes to stderr. Otherwise a lone real file
|
||||||
|
# (either name) is adopted as the mirror's AGENTS.md -- a lone
|
||||||
|
# CLAUDE.md is renamed, never preserved under its own name. Both real and byte-identical: the
|
||||||
|
# AGENTS.md side is adopted and the duplicate CLAUDE.md is dropped.
|
||||||
|
# Both real and different: neither is touched and a warning is
|
||||||
|
# printed to stderr -- this function has no way to know which side
|
||||||
|
# is authoritative, and silently keeping one would silently discard
|
||||||
|
# the other.
|
||||||
|
# 3. Any CLAUDE.md still left in the mirror once AGENTS.md is settled
|
||||||
|
# (belt-and-suspenders past step 1) is removed.
|
||||||
|
# 4. The project-level AGENTS.md symlink is (re)created if missing or
|
||||||
|
# stale, and any CLAUDE.md left at the project level is removed. A
|
||||||
|
# real project-level file found here (written after the mirror
|
||||||
|
# settled) is checked for protection first, as in 2 -- a protected
|
||||||
|
# one is left alone even if byte-identical to the mirror. An
|
||||||
|
# unprotected one is removed only if byte-identical to the mirror; if
|
||||||
|
# it differs, nothing is touched and a warning goes to stderr, as in 2.
|
||||||
|
#
|
||||||
|
# ARGUMENTS
|
||||||
|
# root Absolute path to the project root
|
||||||
|
# agents_dir Absolute path to the project's AGENTS/ sub-repo
|
||||||
|
# rel Path of the directory being synced, relative to root
|
||||||
|
# ("." for the root itself)
|
||||||
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 0 <rel> is settled (including the both-real-and-different and
|
||||||
|
# protected-file skips, which are not failures of this function)
|
||||||
|
# 1 A filesystem operation (mkdir/mv/rm/ln) failed
|
||||||
|
#
|
||||||
|
# RETURNS
|
||||||
|
# One "→ ..." line per change made, on stdout; nothing when <rel> was
|
||||||
|
# already settled. A skip warning goes to stderr, never stdout, so it is
|
||||||
|
# never mistaken for a change.
|
||||||
|
#
|
||||||
|
# EXAMPLE
|
||||||
|
# _agents_init_sync_instructions /path/to/project /path/to/project/AGENTS .
|
||||||
|
# _agents_init_sync_instructions /path/to/project /path/to/project/AGENTS functions
|
||||||
|
function _agents_init_sync_instructions --argument-names root agents_dir rel
|
||||||
|
test -n "$root" -a -n "$agents_dir" -a -n "$rel"; or return 1
|
||||||
|
|
||||||
|
set -l proj_dir "$root"
|
||||||
|
set -l mirror_dir "$agents_dir"
|
||||||
|
if test "$rel" != "."
|
||||||
|
set proj_dir "$root/$rel"
|
||||||
|
set mirror_dir "$agents_dir/$rel"
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l proj_agents "$proj_dir/AGENTS.md"
|
||||||
|
set -l proj_claude "$proj_dir/CLAUDE.md"
|
||||||
|
set -l mirror_agents "$mirror_dir/AGENTS.md"
|
||||||
|
set -l mirror_claude "$mirror_dir/CLAUDE.md"
|
||||||
|
|
||||||
|
# Display names for progress lines: bare at the root, "<rel>/..." below it.
|
||||||
|
set -l disp_agents AGENTS.md
|
||||||
|
set -l disp_claude CLAUDE.md
|
||||||
|
set -l mirror_rel AGENTS
|
||||||
|
if test "$rel" != "."
|
||||||
|
set disp_agents "$rel/AGENTS.md"
|
||||||
|
set disp_claude "$rel/CLAUDE.md"
|
||||||
|
set mirror_rel "AGENTS/$rel"
|
||||||
|
end
|
||||||
|
|
||||||
|
mkdir -p "$mirror_dir"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not create $mirror_dir" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── 1: an inverted mirror (CLAUDE.md real, AGENTS.md symlinked to it) ──
|
||||||
|
if test -f "$mirror_claude"; and not test -L "$mirror_claude"
|
||||||
|
if test -L "$mirror_agents"
|
||||||
|
rm -f "$mirror_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not remove $mirror_agents" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not test -e "$mirror_agents"
|
||||||
|
command mv "$mirror_claude" "$mirror_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not rename $mirror_claude" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Renamed $mirror_rel/CLAUDE.md → AGENTS.md"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── 2: adopt real project-level files, only if the mirror has none yet ──
|
||||||
|
if not test -f "$mirror_agents"
|
||||||
|
set -l has_agents 0
|
||||||
|
set -l has_claude 0
|
||||||
|
test -f "$proj_agents"; and not test -L "$proj_agents"; and set has_agents 1
|
||||||
|
test -f "$proj_claude"; and not test -L "$proj_claude"; and set has_claude 1
|
||||||
|
|
||||||
|
# A deliberately git-tracked file is left alone -- and so is its
|
||||||
|
# sibling, since adopting one of a pair would still relink or drop
|
||||||
|
# the tracked one.
|
||||||
|
set -l protected
|
||||||
|
test $has_agents -eq 1; and _agents_init_path_is_protected "$root" "$proj_agents"; and set -a protected $disp_agents
|
||||||
|
test $has_claude -eq 1; and _agents_init_path_is_protected "$root" "$proj_claude"; and set -a protected $disp_claude
|
||||||
|
if set -q protected[1]
|
||||||
|
echo "_agents_init_sync_instructions: "(string join ', ' -- $protected)" tracked by git; leaving this directory's instruction files untouched" >&2
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if test $has_agents -eq 1; and test $has_claude -eq 1
|
||||||
|
if command diff -q "$proj_agents" "$proj_claude" >/dev/null 2>&1
|
||||||
|
command mv "$proj_agents" "$mirror_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not move $proj_agents" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
rm -f "$proj_claude"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not remove $proj_claude" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Moved $disp_agents → $mirror_rel/AGENTS.md (dropped identical CLAUDE.md)"
|
||||||
|
else
|
||||||
|
echo "_agents_init_sync_instructions: $proj_agents and $proj_claude differ; leaving both, resolve by hand" >&2
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
else if test $has_agents -eq 1
|
||||||
|
command mv "$proj_agents" "$mirror_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not move $proj_agents" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Moved $disp_agents → $mirror_rel/AGENTS.md"
|
||||||
|
else if test $has_claude -eq 1
|
||||||
|
command mv "$proj_claude" "$mirror_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not move $proj_claude" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Moved $disp_claude → $mirror_rel/AGENTS.md"
|
||||||
|
else if test "$rel" = "."
|
||||||
|
printf '%s\n' \
|
||||||
|
'# AGENTS.md' \
|
||||||
|
'' \
|
||||||
|
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
|
||||||
|
'> You may be reading this file via a symlink (`AGENTS.md`) in' \
|
||||||
|
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
|
||||||
|
'> through symlinks and will throw an error.' \
|
||||||
|
'>' \
|
||||||
|
'> **DO NOT** attempt to write to or edit `AGENTS.md` in the' \
|
||||||
|
'> project root. If you need to update these instructions, you **MUST write' \
|
||||||
|
'> directly to `AGENTS/AGENTS.md`**.' >"$mirror_agents"
|
||||||
|
echo "→ Created AGENTS/AGENTS.md with agent directive"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# ── 3: the mirror never carries a CLAUDE.md once AGENTS.md is settled ──
|
||||||
|
if test -f "$mirror_agents"; and test -e "$mirror_claude" -o -L "$mirror_claude"
|
||||||
|
rm -f "$mirror_claude"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not remove $mirror_claude" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Removed $mirror_rel/CLAUDE.md"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Nothing more to do for a conflicted or still-empty directory.
|
||||||
|
test -f "$mirror_agents"; or return 0
|
||||||
|
|
||||||
|
# ── 4: ensure the project-level AGENTS.md symlink, drop project CLAUDE.md ──
|
||||||
|
set -l target "AGENTS/AGENTS.md"
|
||||||
|
if test "$rel" != "."
|
||||||
|
set -l up (string repeat -n (count (string split / -- $rel)) "../")
|
||||||
|
set target "$up""AGENTS/$rel/AGENTS.md"
|
||||||
|
end
|
||||||
|
# A real (non-symlink) file here arrived after the mirror settled. Same
|
||||||
|
# rules as step 2. A deliberately git-tracked one is left alone first,
|
||||||
|
# even if byte-identical: turning a tracked regular file into a symlink
|
||||||
|
# is itself a change to it. Otherwise, byte-identical to the mirror is a
|
||||||
|
# duplicate and is replaced below; different means touch nothing and warn.
|
||||||
|
set -l protected
|
||||||
|
test -f "$proj_agents"; and not test -L "$proj_agents"; and _agents_init_path_is_protected "$root" "$proj_agents"; and set -a protected $disp_agents
|
||||||
|
test -f "$proj_claude"; and not test -L "$proj_claude"; and _agents_init_path_is_protected "$root" "$proj_claude"; and set -a protected $disp_claude
|
||||||
|
if set -q protected[1]
|
||||||
|
echo "_agents_init_sync_instructions: "(string join ', ' -- $protected)" tracked by git; leaving this directory's instruction files untouched" >&2
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
for f in $proj_agents $proj_claude
|
||||||
|
if test -f "$f"; and not test -L "$f"
|
||||||
|
if not command diff -q "$f" "$mirror_agents" >/dev/null 2>&1
|
||||||
|
echo "_agents_init_sync_instructions: $f and $mirror_agents differ; leaving both, resolve by hand" >&2
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l need_link 1
|
||||||
|
if test -L "$proj_agents"
|
||||||
|
test (readlink "$proj_agents") = "$target"; and set need_link 0
|
||||||
|
end
|
||||||
|
if test $need_link -eq 1
|
||||||
|
rm -f "$proj_agents"
|
||||||
|
ln -s "$target" "$proj_agents"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not link $proj_agents" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Linked $disp_agents → $target"
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -e "$proj_claude" -o -L "$proj_claude"
|
||||||
|
rm -f "$proj_claude"
|
||||||
|
or begin
|
||||||
|
echo "_agents_init_sync_instructions: could not remove $proj_claude" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
echo "→ Removed $disp_claude"
|
||||||
|
end
|
||||||
|
|
||||||
|
return 0
|
||||||
|
end
|
||||||
+99
-95
@@ -5,10 +5,10 @@
|
|||||||
# 12-ai-and-developer-tools
|
# 12-ai-and-developer-tools
|
||||||
#
|
#
|
||||||
# DEPENDENCIES
|
# DEPENDENCIES
|
||||||
# _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
|
# _agents_init_sync_instructions, _agents_repo_install_tools, _agents_repo_sync, _agents_init_ensure_gitignore
|
||||||
#
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(rm,mkdir), bypasses-shadow(mv)
|
# self-limiting(rm,mkdir,grep), bypasses-shadow(mv), manual-section(16-agent-tooling)
|
||||||
#
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# agents-init [-a | --agents] [-p | --plugins] [-v | --verbose]
|
# agents-init [-a | --agents] [-p | --plugins] [-v | --verbose]
|
||||||
@@ -17,8 +17,18 @@
|
|||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
|
# Scaffolds an AGENTS/ sub-repository inside a project directory. Creates
|
||||||
# a self-contained git repo for agent specifications, moves any existing
|
# a self-contained git repo for agent specifications, moves any existing
|
||||||
# agent-related files into it, and replaces them with symlinks so the outer
|
# agent-related files into it, and replaces them with symlinks so the
|
||||||
# project never tracks agent files directly.
|
# outer project never tracks agent files directly. This applies at the
|
||||||
|
# project root and, automatically, to any subdirectory that carries its
|
||||||
|
# own scoped AGENTS.md or CLAUDE.md -- discovered by scanning the tree,
|
||||||
|
# not a hardcoded list. The scan prunes dot-directories (.git/, .claude/,
|
||||||
|
# ...), nested repos, AGENTS/ itself, node_modules/, and generated-output
|
||||||
|
# directories (build/, dist/, out/, target/).
|
||||||
|
#
|
||||||
|
# A real instruction file that the project deliberately tracks -- in
|
||||||
|
# git's index, in a project whose .gitignore is non-empty -- is left
|
||||||
|
# exactly where it is, with a warning, rather than moved into AGENTS/ and
|
||||||
|
# replaced by a symlink. See _agents_init_path_is_protected.
|
||||||
#
|
#
|
||||||
# Scaffolding runs only inside a git repository, or in a directory that
|
# Scaffolding runs only inside a git repository, or in a directory that
|
||||||
# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a
|
# already has an AGENTS.md, CLAUDE.md, or AGENTS/. Elsewhere it is a
|
||||||
@@ -26,11 +36,12 @@
|
|||||||
# create a repository there.
|
# create a repository there.
|
||||||
#
|
#
|
||||||
# File layout after setup:
|
# File layout after setup:
|
||||||
# AGENTS/AGENTS.md canonical agent spec (real file)
|
# AGENTS/AGENTS.md canonical root agent spec (real file)
|
||||||
# AGENTS/CLAUDE.md real file (if CLAUDE.md existed separately)
|
# AGENTS/<subdir>/AGENTS.md canonical spec for any subdir with its own
|
||||||
# or symlink → AGENTS.md (single-source case)
|
# scoped instructions (real file, discovered
|
||||||
|
# automatically -- see above)
|
||||||
# <root>/AGENTS.md → AGENTS/AGENTS.md
|
# <root>/AGENTS.md → AGENTS/AGENTS.md
|
||||||
# <root>/CLAUDE.md → AGENTS/CLAUDE.md
|
# <root>/<subdir>/AGENTS.md → AGENTS/<subdir>/AGENTS.md
|
||||||
# AGENTS/plans superpowers plans (real dir, .gitkeep)
|
# AGENTS/plans superpowers plans (real dir, .gitkeep)
|
||||||
# AGENTS/specs superpowers specs (real dir, .gitkeep)
|
# AGENTS/specs superpowers specs (real dir, .gitkeep)
|
||||||
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
|
# AGENTS/devlogs agent development logs (real dir, .gitkeep)
|
||||||
@@ -42,6 +53,12 @@
|
|||||||
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
|
# docs/specs → ../AGENTS/specs (only if docs/specs existed)
|
||||||
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
|
# docs/devlogs → ../AGENTS/devlogs (only if docs/devlogs existed)
|
||||||
#
|
#
|
||||||
|
# No CLAUDE.md survives anywhere in a managed tree: claude-code reads
|
||||||
|
# AGENTS.md natively when CLAUDE.md is absent, so CLAUDE.md exists here
|
||||||
|
# purely as a retirement target -- any found (root or subdirectory, real
|
||||||
|
# file or leftover symlink) is folded into the AGENTS.md-only shape
|
||||||
|
# above by _agents_init_sync_instructions.
|
||||||
|
#
|
||||||
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
|
# plans/ and specs/ are merged from every legacy location (docs/<tgt>,
|
||||||
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
|
# docs/superpowers/<tgt>, and the old AGENTS/plugins/ layout) into the
|
||||||
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
|
# canonical AGENTS/<tgt>; the AGENTS/plugins/ layer is removed.
|
||||||
@@ -75,7 +92,8 @@
|
|||||||
# Called automatically by the claude and agy wrappers on every invocation.
|
# Called automatically by the claude and agy wrappers on every invocation.
|
||||||
#
|
#
|
||||||
# ARGUMENTS
|
# ARGUMENTS
|
||||||
# -a, --agents Set up AGENTS/ repo + AGENTS.md / CLAUDE.md symlinks only
|
# -a, --agents Set up AGENTS/ repo + AGENTS.md symlinks (root and every
|
||||||
|
# discovered subdirectory) only
|
||||||
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
|
# -p, --plugins Set up AGENTS/ repo + plans/specs/devlogs dirs + docs/ symlinks only
|
||||||
# -v, --verbose Print all per-step output (default)
|
# -v, --verbose Print all per-step output (default)
|
||||||
# -q, --quiet Print one summary line only if changes were made
|
# -q, --quiet Print one summary line only if changes were made
|
||||||
@@ -92,6 +110,15 @@
|
|||||||
# agents-init --agents
|
# agents-init --agents
|
||||||
# agents-init --plugins
|
# agents-init --plugins
|
||||||
# agents-init --quiet
|
# agents-init --quiet
|
||||||
|
#
|
||||||
|
# NOTES
|
||||||
|
# This header covers usage only. The full concept/behavior/purpose
|
||||||
|
# write-up -- the AGENTS.md convention, the AGENTS/ sub-repository, the
|
||||||
|
# discovery and safety model, and a complete scenario-by-scenario
|
||||||
|
# reference table -- lives in its own manual section:
|
||||||
|
# docs/manual/16-agent-tooling.md. Update that section in the same
|
||||||
|
# change whenever this function's behavior changes; see "Dedicated
|
||||||
|
# manual sections for complex subsystems" in CONTRIBUTING.md.
|
||||||
function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec files and plugin dirs'
|
function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec files and plugin dirs'
|
||||||
__fish_palette
|
__fish_palette
|
||||||
|
|
||||||
@@ -105,7 +132,7 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
|||||||
echo
|
echo
|
||||||
echo "$c_head""Options:$c_reset"
|
echo "$c_head""Options:$c_reset"
|
||||||
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
||||||
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md / CLAUDE.md symlinks only"
|
echo " $c_flag-a$c_reset, $c_flag--agents$c_reset Set up AGENTS.md symlinks only"
|
||||||
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
|
echo " $c_flag-p$c_reset, $c_flag--plugins$c_reset Set up plans/specs/devlogs dirs and docs/ symlinks only"
|
||||||
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
|
echo " $c_flag-v$c_reset, $c_flag--verbose$c_reset Print all per-step output (default)"
|
||||||
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
|
echo " $c_flag-q$c_reset, $c_flag--quiet$c_reset Print one summary line only if changes were made"
|
||||||
@@ -140,7 +167,9 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
|||||||
# directory created an AGENTS/ repo, two root symlinks, and a docs/
|
# directory created an AGENTS/ repo, two root symlinks, and a docs/
|
||||||
# tree there.
|
# tree there.
|
||||||
set -l root (git rev-parse --show-toplevel 2>/dev/null)
|
set -l root (git rev-parse --show-toplevel 2>/dev/null)
|
||||||
|
set -l in_git 1
|
||||||
if test -z "$root"
|
if test -z "$root"
|
||||||
|
set in_git 0
|
||||||
if test -e (pwd)/AGENTS.md -o -e (pwd)/CLAUDE.md -o -d (pwd)/AGENTS
|
if test -e (pwd)/AGENTS.md -o -e (pwd)/CLAUDE.md -o -d (pwd)/AGENTS
|
||||||
set root (pwd)
|
set root (pwd)
|
||||||
else
|
else
|
||||||
@@ -201,109 +230,84 @@ function agents-init --description 'scaffold AGENTS/ sub-repo with agent spec fi
|
|||||||
|
|
||||||
# ──────────────────────────── --agents mode ──────────────────────────────
|
# ──────────────────────────── --agents mode ──────────────────────────────
|
||||||
if test $do_agents -eq 1
|
if test $do_agents -eq 1
|
||||||
# Detect which root-level files are real (not symlinks)
|
# Discover every directory carrying agent instructions -- root
|
||||||
set -l has_agents 0
|
# included, subdirectories found automatically rather than by a
|
||||||
set -l has_claude 0
|
# hardcoded list. A real file, an already-migrated symlink, or a
|
||||||
if test -f "$root/AGENTS.md"; and not test -L "$root/AGENTS.md"
|
# leftover inverted-mirror survivor all match, so one pass covers
|
||||||
set has_agents 1
|
# fresh, migrated, and legacy state alike.
|
||||||
|
#
|
||||||
|
# Discovery stays inside this project: a non-git root (a lone
|
||||||
|
# agent file in, say, ~) syncs only itself -- walking it would
|
||||||
|
# reach into every unrelated tree below. In a git root, pruned:
|
||||||
|
# any AGENTS/ (a mirror, never a source), dot-directories (.git,
|
||||||
|
# .claude, .github: tool state, not scoped project dirs),
|
||||||
|
# node_modules, generated-output directories (build, dist, out,
|
||||||
|
# target: an instruction file there is a build artifact, never a
|
||||||
|
# source -- pruned outright, before tracked-file protection would
|
||||||
|
# even be consulted), and nested repos/submodules/worktrees (their
|
||||||
|
# own .git marks another project). -mindepth 1 keeps the root
|
||||||
|
# itself, which has a .git, from pruning the whole walk.
|
||||||
|
set -l found
|
||||||
|
if test $in_git -eq 1
|
||||||
|
set found (find "$root" -mindepth 1 \
|
||||||
|
-type d \( -name '.*' -o -name AGENTS -o -name node_modules \
|
||||||
|
-o -name build -o -name dist -o -name out -o -name target \
|
||||||
|
-o -exec test -e '{}/.git' \; \) -prune -o \
|
||||||
|
\( -name AGENTS.md -o -name CLAUDE.md \) -print)
|
||||||
end
|
end
|
||||||
if test -f "$root/CLAUDE.md"; and not test -L "$root/CLAUDE.md"
|
set -l rels "."
|
||||||
set has_claude 1
|
for f in $found
|
||||||
|
set -l d (path dirname "$f")
|
||||||
|
set -l rel (string replace "$root/" "" "$d")
|
||||||
|
test "$rel" = "$d"; and set rel "."
|
||||||
|
contains -- "$rel" $rels; or set -a rels "$rel"
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── Move real files into AGENTS/ ──────────────────────────────────────
|
for rel in $rels
|
||||||
if test $has_agents -eq 1; and test $has_claude -eq 1
|
set -l out (_agents_init_sync_instructions "$root" "$agents_dir" "$rel")
|
||||||
# Both exist: preserve each as its own file in AGENTS/
|
set -l rc $status
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
if test $rc -ne 0
|
||||||
if not command mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
|
echo "$c_err""Error: could not sync AGENTS.md for $rel$c_reset" >&2
|
||||||
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
if test -n "$out"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
|
if test $verbose -eq 1
|
||||||
|
for line in $out
|
||||||
|
echo "$c_ok$line$c_reset"
|
||||||
end
|
end
|
||||||
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
|
|
||||||
if not command mv "$root/CLAUDE.md" "$agents_dir/CLAUDE.md"
|
|
||||||
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/CLAUDE.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
end
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/CLAUDE.md$c_reset"
|
|
||||||
end
|
|
||||||
else if test $has_agents -eq 1
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
if not command mv "$root/AGENTS.md" "$agents_dir/AGENTS.md"
|
|
||||||
echo "$c_err""Error: could not move AGENTS.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved AGENTS.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
else if test $has_claude -eq 1
|
|
||||||
# Only CLAUDE.md: treat it as the agent spec
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
if not command mv "$root/CLAUDE.md" "$agents_dir/AGENTS.md"
|
|
||||||
echo "$c_err""Error: could not move CLAUDE.md → AGENTS/AGENTS.md$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Moved CLAUDE.md → AGENTS/AGENTS.md$c_reset"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Neither exists: create AGENTS/AGENTS.md with the agent directive
|
|
||||||
if not test -f "$agents_dir/AGENTS.md"
|
|
||||||
printf '%s\n' \
|
|
||||||
'# AGENTS.md' \
|
|
||||||
'' \
|
|
||||||
'> ⚠️ **SYSTEM DIRECTIVE FOR AI AGENTS: FILE EDITING**' \
|
|
||||||
'> You may be reading this file via a symlink (`CLAUDE.md` or `AGENTS.md`) in' \
|
|
||||||
'> the root of the project. Your environment'\''s file-editing tools cannot write' \
|
|
||||||
'> through symlinks and will throw an error.' \
|
|
||||||
'>' \
|
|
||||||
'> **DO NOT** attempt to write to or edit `CLAUDE.md` or `AGENTS.md` in the' \
|
|
||||||
'> project root. If you need to update these instructions, you **MUST write' \
|
|
||||||
'> directly to `AGENTS/AGENTS.md`**.' >"$agents_dir/AGENTS.md"
|
|
||||||
set changed 1
|
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Created AGENTS/AGENTS.md with agent directive$c_reset"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── Ensure AGENTS/CLAUDE.md exists ────────────────────────────────────
|
# ── Migrate stale anchored gitignore lines ──────────────────────────────
|
||||||
# When both files existed, AGENTS/CLAUDE.md is already a real file.
|
# A project scaffolded by the old agents-init already has anchored
|
||||||
# Otherwise, create it as a symlink → AGENTS.md (within AGENTS/).
|
# /AGENTS.md and/or /CLAUDE.md lines in .gitignore. git check-ignore
|
||||||
if not test -f "$agents_dir/CLAUDE.md"; and not test -L "$agents_dir/CLAUDE.md"
|
# sees those as covering the literal path "AGENTS.md", so the new
|
||||||
if not ln -s AGENTS.md "$agents_dir/CLAUDE.md"
|
# unanchored pattern below would be judged already-covered and never
|
||||||
echo "$c_err""Error: could not create AGENTS/CLAUDE.md symlink$c_reset" >&2
|
# added -- leaving any newly discovered subdirectory AGENTS.md with no
|
||||||
return 1
|
# gitignore coverage at all. Strip the stale exact lines first so the
|
||||||
end
|
# unanchored pattern always gets a chance to be added. No-op when
|
||||||
|
# neither stale line is present.
|
||||||
|
set -l gitignore "$root/.gitignore"
|
||||||
|
if test -f "$gitignore"
|
||||||
|
if grep -qxF "/AGENTS.md" "$gitignore"
|
||||||
|
sed -i '/^\/AGENTS\.md$/d' "$gitignore"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Linked AGENTS/CLAUDE.md → AGENTS/AGENTS.md$c_reset"
|
test $verbose -eq 1; and echo "$c_warn→ Removed stale /AGENTS.md line from .gitignore$c_reset"
|
||||||
end
|
|
||||||
|
|
||||||
# Root symlinks point at files, not directories, so they cannot use
|
|
||||||
# _agents_repo_ensure_symlink (which is directory-only by design).
|
|
||||||
for pair in "AGENTS.md:AGENTS/AGENTS.md" "CLAUDE.md:AGENTS/CLAUDE.md"
|
|
||||||
set -l name (string split -f1 ':' -- $pair)
|
|
||||||
set -l want (string split -f2 ':' -- $pair)
|
|
||||||
set -l need 0
|
|
||||||
if not test -L "$root/$name"
|
|
||||||
set need 1
|
|
||||||
else if test (readlink "$root/$name") != "$want"
|
|
||||||
rm -f "$root/$name"
|
|
||||||
set need 1
|
|
||||||
end
|
|
||||||
if test $need -eq 1
|
|
||||||
if not ln -s "$want" "$root/$name"
|
|
||||||
echo "$c_err""Error: could not create $name symlink$c_reset" >&2
|
|
||||||
return 1
|
|
||||||
end
|
end
|
||||||
|
if grep -qxF "/CLAUDE.md" "$gitignore"
|
||||||
|
sed -i '/^\/CLAUDE\.md$/d' "$gitignore"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo "$c_ok→ Linked $name → $want$c_reset"
|
test $verbose -eq 1; and echo "$c_warn→ Removed stale /CLAUDE.md line from .gitignore$c_reset"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── .gitignore ────────────────────────────────────────────────────────
|
# ── .gitignore ────────────────────────────────────────────────────────
|
||||||
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "/AGENTS.md" "/CLAUDE.md")
|
# Unanchored: matches AGENTS.md at every depth, so a newly
|
||||||
|
# discovered subdirectory needs no additional gitignore entry.
|
||||||
|
# CLAUDE.md is dropped entirely -- nothing creates one anymore.
|
||||||
|
set -l _gi (_agents_init_ensure_gitignore "$root" "agents-init --agents" "AGENTS/" "AGENTS.md")
|
||||||
if test -n "$_gi"
|
if test -n "$_gi"
|
||||||
set changed 1
|
set changed 1
|
||||||
test $verbose -eq 1; and echo $_gi
|
test $verbose -eq 1; and echo $_gi
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
# _agents_repo_install_tools, git, hostname
|
# _agents_repo_install_tools, git, hostname
|
||||||
#
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(rm,mkdir)
|
# self-limiting(rm,mkdir), manual-section(16-agent-tooling)
|
||||||
#
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# agents-vault [--link] [--push] [--restore] [--status]
|
# agents-vault [--link] [--push] [--restore] [--status]
|
||||||
@@ -188,6 +188,13 @@
|
|||||||
# machine that has a real global memory directory would move it into a
|
# machine that has a real global memory directory would move it into a
|
||||||
# throwaway directory and leave a dangling symlink behind, which is
|
# throwaway directory and leave a dangling symlink behind, which is
|
||||||
# strictly worse than having had no backup at all.
|
# strictly worse than having had no backup at all.
|
||||||
|
#
|
||||||
|
# This header covers usage only. The full concept/behavior/purpose
|
||||||
|
# write-up -- how this relates to the per-project AGENTS/ repository
|
||||||
|
# agents-init manages, and where each kind of agent state actually lives
|
||||||
|
# -- is in docs/manual/16-agent-tooling.md. Update that section in the
|
||||||
|
# same change whenever this function's behavior changes; see "Dedicated
|
||||||
|
# manual sections for complex subsystems" in CONTRIBUTING.md.
|
||||||
function agents-vault --description 'track curated agent memory in a host-scoped vault repo'
|
function agents-vault --description 'track curated agent memory in a host-scoped vault repo'
|
||||||
__fish_palette
|
__fish_palette
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -17,8 +17,8 @@
|
|||||||
# Wrapper for the agy Antigravity AI CLI that ensures the AGENTS/
|
# Wrapper for the agy Antigravity AI CLI that ensures the AGENTS/
|
||||||
# sub-repository is initialized and any agent-made changes are committed
|
# sub-repository is initialized and any agent-made changes are committed
|
||||||
# before launch. Delegates all scaffold and commit logic to agents-init
|
# before launch. Delegates all scaffold and commit logic to agents-init
|
||||||
# --quiet (full setup), which ensures AGENTS/ is scaffolded and CLAUDE.md
|
# --quiet (full setup), which ensures AGENTS.md (root and every scoped
|
||||||
# is symlinked to AGENTS/AGENTS.md in the current project.
|
# subdirectory) is symlinked into AGENTS/ in the current project.
|
||||||
#
|
#
|
||||||
# Also syncs the host-scoped agent memory vault (agents-vault). agy has
|
# 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
|
# no session-end hook, so its memory is captured on the next launch
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# autoexec/sync
|
# autoexec/sync
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# auto-pull [list]
|
# auto-pull [list]
|
||||||
# auto-pull add [PATH]
|
# auto-pull add [PATH]
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl, jq, git
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# bd-pull <owner/repo>
|
# bd-pull <owner/repo>
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 08-terminal-management
|
# 08-terminal-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# nohup
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# bkg <command> [args...]
|
# bkg <command> [args...]
|
||||||
#
|
#
|
||||||
@@ -32,6 +35,11 @@ function bkg --description 'Execute bkg'
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not type -q nohup
|
||||||
|
echo (set_color red)"Error: nohup is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
# Run the command using nohup to make it immune to hangups (like closing the terminal).
|
# Run the command using nohup to make it immune to hangups (like closing the terminal).
|
||||||
# Redirect both stdout and stderr to /dev/null to discard all output.
|
# Redirect both stdout and stderr to /dev/null to discard all output.
|
||||||
# The final ampersand (&) sends the entire process to the background.
|
# The final ampersand (&) sends the entire process to the background.
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 04-git-and-version-control
|
# 04-git-and-version-control
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# branch <branch_name>
|
# branch <branch_name>
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/filesystem
|
# aliases/filesystem
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# bat
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(ls), bypasses-shadow(cat)
|
# uses-shadow(ls), bypasses-shadow(cat)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 02-navigation
|
# 02-navigation
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# zoxide
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# cdi [query]
|
# cdi [query]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fastfetch, neofetch
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(ls)
|
# uses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# cheat, tldr, man
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# cheat <topic> [args...]
|
# cheat <topic> [args...]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 06-dependency-management
|
# 06-dependency-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fish-deps
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# check_fish_deps
|
# check_fish_deps
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
# Wrapper for the claude CLI that ensures the AGENTS/ sub-repository is
|
# Wrapper for the claude CLI that ensures the AGENTS/ sub-repository is
|
||||||
# initialized and any agent-made changes are committed before launch.
|
# initialized and any agent-made changes are committed before launch.
|
||||||
# Delegates all scaffold and commit logic to agents-init --quiet (full
|
# Delegates all scaffold and commit logic to agents-init --quiet (full
|
||||||
# setup), which ensures AGENTS/ is scaffolded and CLAUDE.md is symlinked
|
# setup), which ensures AGENTS.md (root and every scoped subdirectory)
|
||||||
# to AGENTS/AGENTS.md in the current project.
|
# is symlinked into AGENTS/ in the current project. claude-code reads
|
||||||
|
# AGENTS.md natively, so no CLAUDE.md is created or maintained.
|
||||||
#
|
#
|
||||||
# Also syncs the host-scoped agent memory vault (agents-vault), which
|
# Also syncs the host-scoped agent memory vault (agents-vault), which
|
||||||
# tracks curated memory living outside the project tree. The vault
|
# tracks curated memory living outside the project tree. The vault
|
||||||
@@ -44,7 +45,7 @@
|
|||||||
# claude
|
# claude
|
||||||
# claude --resume
|
# claude --resume
|
||||||
# claude "Explain the recent changes"
|
# claude "Explain the recent changes"
|
||||||
function claude --wraps=claude --description 'claude wrapper: auto-links AGENTS.md as CLAUDE.md'
|
function claude --wraps=claude --description 'claude wrapper: ensures AGENTS/ is scaffolded before launch'
|
||||||
if not __fish_config_op_enabled (status current-function)
|
if not __fish_config_op_enabled (status current-function)
|
||||||
command claude $argv
|
command claude $argv
|
||||||
return $status
|
return $status
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 05-package-management
|
# 05-package-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# pacman
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(grep)
|
# self-limiting(grep)
|
||||||
#
|
#
|
||||||
|
|||||||
+11
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 02-navigation
|
# 02-navigation
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# clone-in-kitty
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# clone [args...]
|
# clone [args...]
|
||||||
#
|
#
|
||||||
@@ -16,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# 0 Repository cloned
|
# 0 Repository cloned
|
||||||
# 1 Not running inside Kitty terminal
|
# 1 Not running inside Kitty terminal, or clone-in-kitty isn't available
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# clone https://github.com/user/repo.git
|
# clone https://github.com/user/repo.git
|
||||||
@@ -25,5 +28,12 @@ function clone --wraps='clone-in-kitty' --description 'alias clone=clone-in-kitt
|
|||||||
echo "Error: The 'clone' command requires Kitty terminal." >&2
|
echo "Error: The 'clone' command requires Kitty terminal." >&2
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
# $TERM only proves the terminal type -- clone-in-kitty is a function
|
||||||
|
# Kitty's own shell integration injects, which doesn't happen over an
|
||||||
|
# ssh session that merely inherits $TERM from the local Kitty.
|
||||||
|
if not type -q clone-in-kitty
|
||||||
|
echo "Error: 'clone' detected Kitty but clone-in-kitty isn't available (shell integration not loaded)." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
clone-in-kitty $argv
|
clone-in-kitty $argv
|
||||||
end
|
end
|
||||||
|
|||||||
+11
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 02-navigation
|
# 02-navigation
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# clone-in-kitty
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# clonet [args...]
|
# clonet [args...]
|
||||||
#
|
#
|
||||||
@@ -16,7 +19,7 @@
|
|||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# 0 Repository cloned
|
# 0 Repository cloned
|
||||||
# 1 Not running inside Kitty terminal
|
# 1 Not running inside Kitty terminal, or clone-in-kitty isn't available
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# clonet https://github.com/user/repo.git
|
# clonet https://github.com/user/repo.git
|
||||||
@@ -25,5 +28,12 @@ function clonet --wraps='clone-in-kitty --type=tab' --description 'alias clonet=
|
|||||||
echo "Error: The 'clonet' command requires Kitty terminal." >&2
|
echo "Error: The 'clonet' command requires Kitty terminal." >&2
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
# $TERM only proves the terminal type -- clone-in-kitty is a function
|
||||||
|
# Kitty's own shell integration injects, which doesn't happen over an
|
||||||
|
# ssh session that merely inherits $TERM from the local Kitty.
|
||||||
|
if not type -q clone-in-kitty
|
||||||
|
echo "Error: 'clonet' detected Kitty but clone-in-kitty isn't available (shell integration not loaded)." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
clone-in-kitty --type=tab $argv
|
clone-in-kitty --type=tab $argv
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# xdg-open, man, ov, bat
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(grep), bypasses-shadow(less)
|
# self-limiting(grep), bypasses-shadow(less)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# config-update [-h | --help] [-f | --force] [-n | --dry-run]
|
# config-update [-h | --help] [-f | --force] [-n | --dry-run]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 08-terminal-management
|
# 08-terminal-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# nohup
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# detach [-h] [--version] <command> [args...]
|
# detach [-h] [--version] <command> [args...]
|
||||||
#
|
#
|
||||||
@@ -66,5 +69,10 @@ function detach --description 'Execute detach'
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not type -q nohup
|
||||||
|
echo (set_color red)"Error: nohup is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
nohup $args >/dev/null 2>&1 &
|
nohup $args >/dev/null 2>&1 &
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 13-media-and-utilities
|
# 13-media-and-utilities
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# magick, ffmpeg, avifenc, exiftool
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(rm)
|
# self-limiting(rm)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# docker
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/filesystem
|
# aliases/filesystem
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# duf, dust, dua
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(du)
|
# bypasses-shadow(du)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/dev-tools
|
# aliases/dev-tools
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# p, nano, nvim
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(rm)
|
# bypasses-shadow(rm)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 10-network
|
# 10-network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fast
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# fast-cli [args...]
|
# fast-cli [args...]
|
||||||
#
|
#
|
||||||
@@ -13,8 +16,16 @@
|
|||||||
# ARGUMENTS
|
# ARGUMENTS
|
||||||
# args... Arguments forwarded to the fast command
|
# args... Arguments forwarded to the fast command
|
||||||
#
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 1 fast is not installed
|
||||||
|
# * Exit status of fast otherwise
|
||||||
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# fast-cli
|
# fast-cli
|
||||||
function fast-cli --description "Run a speed test using fast.com"
|
function fast-cli --description "Run a speed test using fast.com"
|
||||||
|
if not type -q -f fast
|
||||||
|
echo (set_color red)"Error: fast is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
command fast $argv
|
command fast $argv
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fastfetch, neofetch
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(ls)
|
# uses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 06-dependency-management
|
# 06-dependency-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fish_deps_status, _fish_deps_install, _fish_deps_update
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# fish-deps [status|install|update|sync] [--optional] [--terminals] [--all]
|
# fish-deps [status|install|update|sync] [--optional] [--terminals] [--all]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# overrides/prompt
|
# overrides/prompt
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# docker, starship
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# fish_right_prompt
|
# fish_right_prompt
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -1,251 +0,0 @@
|
|||||||
function fisher --argument-names cmd --description "A plugin manager for Fish"
|
|
||||||
set --query fisher_path || set --local fisher_path $__fish_config_dir
|
|
||||||
set --local fisher_version 4.4.8
|
|
||||||
set --local fish_plugins $__fish_config_dir/fish_plugins
|
|
||||||
|
|
||||||
switch "$cmd"
|
|
||||||
case -v --version
|
|
||||||
echo "fisher, version $fisher_version"
|
|
||||||
case "" -h --help
|
|
||||||
echo "Usage: fisher install <plugins...> Install plugins"
|
|
||||||
echo " fisher remove <plugins...> Remove installed plugins"
|
|
||||||
echo " fisher uninstall <plugins...> Remove installed plugins (alias)"
|
|
||||||
echo " fisher update <plugins...> Update installed plugins"
|
|
||||||
echo " fisher update Update all installed plugins"
|
|
||||||
echo " fisher list [<regex>] List installed plugins matching regex"
|
|
||||||
echo "Options:"
|
|
||||||
echo " -v, --version Print version"
|
|
||||||
echo " -h, --help Print this help message"
|
|
||||||
echo "Variables:"
|
|
||||||
echo " \$fisher_path Plugin installation path. Default: $__fish_config_dir" | string replace --regex -- $HOME \~
|
|
||||||
case ls list
|
|
||||||
string match --entire --regex -- "$argv[2]" $_fisher_plugins
|
|
||||||
case install update remove uninstall
|
|
||||||
isatty || read --local --null --array stdin && set --append argv $stdin
|
|
||||||
|
|
||||||
test "$cmd" = uninstall && set cmd remove
|
|
||||||
|
|
||||||
set --local install_plugins
|
|
||||||
set --local update_plugins
|
|
||||||
set --local remove_plugins
|
|
||||||
set --local arg_plugins $argv[2..-1]
|
|
||||||
set --local old_plugins $_fisher_plugins
|
|
||||||
set --local new_plugins
|
|
||||||
|
|
||||||
test -e $fish_plugins && set --local file_plugins (string match --regex -- '^[^\s]+$' <$fish_plugins | string replace -- \~ ~)
|
|
||||||
|
|
||||||
if ! set --query argv[2]
|
|
||||||
if test "$cmd" != update
|
|
||||||
echo "fisher: Not enough arguments for command: \"$cmd\"" >&2 && return 1
|
|
||||||
else if ! set --query file_plugins
|
|
||||||
echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1
|
|
||||||
end
|
|
||||||
set arg_plugins $file_plugins
|
|
||||||
else if test "$cmd" = install && ! set --query old_plugins[1]
|
|
||||||
set --append arg_plugins $file_plugins
|
|
||||||
end
|
|
||||||
|
|
||||||
for plugin in $arg_plugins
|
|
||||||
set plugin (test -e "$plugin" && realpath $plugin || string lower -- $plugin)
|
|
||||||
contains -- "$plugin" $new_plugins || set --append new_plugins $plugin
|
|
||||||
end
|
|
||||||
|
|
||||||
if set --query argv[2]
|
|
||||||
for plugin in $new_plugins
|
|
||||||
if contains -- "$plugin" $old_plugins
|
|
||||||
test "$cmd" = remove &&
|
|
||||||
set --append remove_plugins $plugin ||
|
|
||||||
set --append update_plugins $plugin
|
|
||||||
else if test "$cmd" = install
|
|
||||||
set --append install_plugins $plugin
|
|
||||||
else
|
|
||||||
echo "fisher: Plugin not installed: \"$plugin\"" >&2 && return 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for plugin in $new_plugins
|
|
||||||
contains -- "$plugin" $old_plugins &&
|
|
||||||
set --append update_plugins $plugin ||
|
|
||||||
set --append install_plugins $plugin
|
|
||||||
end
|
|
||||||
|
|
||||||
for plugin in $old_plugins
|
|
||||||
contains -- "$plugin" $new_plugins || set --append remove_plugins $plugin
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
set --local pid_list
|
|
||||||
set --local source_plugins
|
|
||||||
set --local fetch_plugins $update_plugins $install_plugins
|
|
||||||
set --local fish_path (status fish-path)
|
|
||||||
|
|
||||||
echo (set_color --bold)fisher $cmd version $fisher_version(set_color normal)
|
|
||||||
|
|
||||||
for plugin in $fetch_plugins
|
|
||||||
set --local source (command mktemp -d)
|
|
||||||
set --append source_plugins $source
|
|
||||||
|
|
||||||
command mkdir -p $source/{completions,conf.d,themes,functions}
|
|
||||||
|
|
||||||
$fish_path --command "
|
|
||||||
if test -e $plugin
|
|
||||||
command cp -Rf $plugin/* $source
|
|
||||||
else
|
|
||||||
set resp (command mktemp)
|
|
||||||
set temp (command mktemp -d)
|
|
||||||
set repo (string split -- \@ $plugin) || set repo[2] HEAD
|
|
||||||
|
|
||||||
if set path (string replace --regex -- '^(https://)?gitlab.com/' '' \$repo[1])
|
|
||||||
set name (string split -- / \$path)[-1]
|
|
||||||
set url https://gitlab.com/\$path/-/archive/\$repo[2]/\$name-\$repo[2].tar.gz
|
|
||||||
else
|
|
||||||
set url https://api.github.com/repos/\$repo[1]/tarball/\$repo[2]
|
|
||||||
end
|
|
||||||
|
|
||||||
echo Fetching (set_color --underline)\$url(set_color normal)
|
|
||||||
|
|
||||||
set http (command curl -q --silent -L -o \$resp -w %{http_code} \$url)
|
|
||||||
|
|
||||||
if test \"\$http\" = 200 && command tar -xzC \$temp -f \$resp 2>/dev/null
|
|
||||||
command cp -Rf \$temp/*/* $source
|
|
||||||
else if test \"\$http\" = 403
|
|
||||||
echo fisher: GitHub API rate limit exceeded \(HTTP 403\) >&2
|
|
||||||
command rm -rf $source
|
|
||||||
else
|
|
||||||
echo fisher: Invalid plugin name or host unavailable: \\\"$plugin\\\" >&2
|
|
||||||
command rm -rf $source
|
|
||||||
end
|
|
||||||
|
|
||||||
command rm -rf \$temp
|
|
||||||
end
|
|
||||||
|
|
||||||
set files $source/* && string match --quiet --regex -- .+\.fish\\\$ \$files
|
|
||||||
" &
|
|
||||||
|
|
||||||
set --append pid_list (jobs --last --pid)
|
|
||||||
end
|
|
||||||
|
|
||||||
wait $pid_list 2>/dev/null
|
|
||||||
|
|
||||||
for plugin in $fetch_plugins
|
|
||||||
if set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)] && test ! -e $source
|
|
||||||
if set --local index (contains --index -- "$plugin" $install_plugins)
|
|
||||||
set --erase install_plugins[$index]
|
|
||||||
else
|
|
||||||
set --erase update_plugins[(contains --index -- "$plugin" $update_plugins)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for plugin in $update_plugins $remove_plugins
|
|
||||||
if set --local index (contains --index -- "$plugin" $_fisher_plugins)
|
|
||||||
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
|
|
||||||
|
|
||||||
if contains -- "$plugin" $remove_plugins
|
|
||||||
for name in (string replace --filter --regex -- '.+/conf\.d/([^/]+)\.fish$' '$1' $$plugin_files_var)
|
|
||||||
emit {$name}_uninstall
|
|
||||||
end
|
|
||||||
printf "%s\n" Removing\ (set_color red --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
|
|
||||||
set --erase _fisher_plugins[$index]
|
|
||||||
end
|
|
||||||
|
|
||||||
command rm -rf (string replace -- \~ ~ $$plugin_files_var)
|
|
||||||
|
|
||||||
functions --erase (string replace --filter --regex -- '.+/functions/([^/]+)\.fish$' '$1' $$plugin_files_var)
|
|
||||||
|
|
||||||
for name in (string replace --filter --regex -- '.+/completions/([^/]+)\.fish$' '$1' $$plugin_files_var)
|
|
||||||
complete --erase --command $name
|
|
||||||
end
|
|
||||||
|
|
||||||
set --erase $plugin_files_var
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if set --query update_plugins[1] || set --query install_plugins[1]
|
|
||||||
command mkdir -p $fisher_path/{functions,themes,conf.d,completions}
|
|
||||||
end
|
|
||||||
|
|
||||||
for plugin in $update_plugins $install_plugins
|
|
||||||
set --local source $source_plugins[(contains --index -- "$plugin" $fetch_plugins)]
|
|
||||||
set --local files $source/{functions,themes,conf.d,completions}/*
|
|
||||||
|
|
||||||
if set --local index (contains --index -- $plugin $install_plugins)
|
|
||||||
set --local user_files $fisher_path/{functions,themes,conf.d,completions}/*
|
|
||||||
set --local conflict_files
|
|
||||||
|
|
||||||
for file in (string replace -- $source/ $fisher_path/ $files)
|
|
||||||
contains -- $file $user_files && set --append conflict_files $file
|
|
||||||
end
|
|
||||||
|
|
||||||
if set --query conflict_files[1] && set --erase install_plugins[$index]
|
|
||||||
echo -s "fisher: Cannot install \"$plugin\": please remove or move conflicting files first:" \n" "$conflict_files >&2
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for file in (string replace -- $source/ "" $files)
|
|
||||||
command cp -RLf $source/$file $fisher_path/$file
|
|
||||||
end
|
|
||||||
|
|
||||||
set --local plugin_files_var _fisher_(string escape --style=var -- $plugin)_files
|
|
||||||
|
|
||||||
set --query files[1] && set --universal $plugin_files_var (string replace -- $source $fisher_path $files | string replace -- ~ \~)
|
|
||||||
|
|
||||||
contains -- $plugin $_fisher_plugins || set --universal --append _fisher_plugins $plugin
|
|
||||||
contains -- $plugin $install_plugins && set --local event install || set --local event update
|
|
||||||
|
|
||||||
printf "%s\n" Installing\ (set_color --bold)$plugin(set_color normal) " "$$plugin_files_var | string replace -- \~ ~
|
|
||||||
|
|
||||||
for file in (string match --regex -- '.+/[^/]+\.fish$' $$plugin_files_var | string replace -- \~ ~)
|
|
||||||
source $file
|
|
||||||
if set --local name (string replace --regex -- '.+conf\.d/([^/]+)\.fish$' '$1' $file)
|
|
||||||
emit {$name}_$event
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
command rm -rf $source_plugins
|
|
||||||
|
|
||||||
if set --query _fisher_plugins[1]
|
|
||||||
set --local commit_plugins
|
|
||||||
|
|
||||||
for plugin in $file_plugins
|
|
||||||
contains -- (string lower -- $plugin) (string lower -- $_fisher_plugins) && set --append commit_plugins $plugin
|
|
||||||
end
|
|
||||||
|
|
||||||
for plugin in $_fisher_plugins
|
|
||||||
contains -- (string lower -- $plugin) (string lower -- $commit_plugins) || set --append commit_plugins $plugin
|
|
||||||
end
|
|
||||||
|
|
||||||
string replace --regex -- $HOME \~ $commit_plugins >$fish_plugins
|
|
||||||
else
|
|
||||||
set --erase _fisher_plugins
|
|
||||||
command rm -f $fish_plugins
|
|
||||||
end
|
|
||||||
|
|
||||||
set --local total (count $install_plugins) (count $update_plugins) (count $remove_plugins)
|
|
||||||
|
|
||||||
test "$total" != "0 0 0" && echo (string join ", " (
|
|
||||||
test $total[1] = 0 || echo "Installed $total[1]") (
|
|
||||||
test $total[2] = 0 || echo "Updated $total[2]") (
|
|
||||||
test $total[3] = 0 || echo "Removed $total[3]")
|
|
||||||
) plugin/s
|
|
||||||
case \*
|
|
||||||
echo "fisher: Unknown command: \"$cmd\"" >&2 && return 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if ! set --query _fisher_upgraded_to_4_4
|
|
||||||
set --universal _fisher_upgraded_to_4_4
|
|
||||||
if functions --query _fisher_list
|
|
||||||
set --query XDG_DATA_HOME[1] || set --local XDG_DATA_HOME ~/.local/share
|
|
||||||
command rm -rf $XDG_DATA_HOME/fisher
|
|
||||||
functions --erase _fisher_{list,plugin_parse}
|
|
||||||
fisher update >/dev/null 2>/dev/null
|
|
||||||
else
|
|
||||||
for var in (set --names | string match --entire --regex '^_fisher_.+_files$')
|
|
||||||
set $var (string replace -- ~ \~ $$var)
|
|
||||||
end
|
|
||||||
functions --erase _fisher_fish_postexec
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 06-dependency-management
|
# 06-dependency-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git, fzf
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fzf_search_directory, _fzf_search_git_log, _fzf_search_git_status,
|
||||||
|
# _fzf_search_history, _fzf_search_processes, _fzf_search_variables
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# fzf_configure_bindings [--directory=<key>] [--git_log=<key>] [--git_status=<key>]
|
# fzf_configure_bindings [--directory=<key>] [--git_log=<key>] [--git_status=<key>]
|
||||||
# [--history=<key>] [--processes=<key>] [--variables=<key>] [-h]
|
# [--history=<key>] [--processes=<key>] [--variables=<key>] [-h]
|
||||||
|
|||||||
+118
-27
@@ -4,41 +4,53 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 04-git-and-version-control
|
# 04-git-and-version-control
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl, md5sum, md5, gitignore-scrub
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(grep,cat), network, blocking-prompt
|
# self-limiting(grep,cat), network, blocking-prompt
|
||||||
#
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# gi [-h] [-b] [-p] [-s] [-l] [targets...]
|
# gi [-h] [-b] [-p] [-o] [-s] [-f] [-c TEMPLATE] [-l] [targets...]
|
||||||
#
|
#
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
# Generates .gitignore content by querying the gitignore.io API. Appends
|
# Generates .gitignore content by querying the gitignore.io API. Appends
|
||||||
# results to the repository's .gitignore with MD5-based deduplication —
|
# results to the repository's .gitignore with MD5-based deduplication —
|
||||||
# patterns already present are not re-appended — or prints to stdout with
|
# patterns already present are not re-appended — or prints to stdout with
|
||||||
# -s. Supports generic boilerplate and interactive prompt modes.
|
# -o/--stdout. Boilerplate mode uses $GITIGNORE_BOILERPLATE if set, a
|
||||||
|
# -c/--custom template if given, or falls back to the bundled standard
|
||||||
|
# template (data/gi/boilerplate.gitignore) when neither is configured.
|
||||||
|
# Supports generic boilerplate and interactive prompt modes.
|
||||||
#
|
#
|
||||||
# ARGUMENTS
|
# ARGUMENTS
|
||||||
# -h, --help Show help message
|
# -h, --help Show help message
|
||||||
# -d, --description Show the function description
|
# -d, --description Show the function description
|
||||||
# -l, --list List all supported targets from the API
|
# -l, --list List all supported targets from the API
|
||||||
# -b, --boilerplate Append boilerplate from $GITIGNORE_BOILERPLATE
|
# -b, --boilerplate Append boilerplate (implied by -c)
|
||||||
# -p, --prompt Prompt for patterns to append
|
# -p, --prompt Prompt for patterns to append
|
||||||
# -s, --stdout Print API output to stdout instead of .gitignore
|
# -o, --stdout Print generated content to stdout instead of .gitignore
|
||||||
|
# -s, --silent Suppress progress output (errors and prompts still show)
|
||||||
|
# -f, --force Bypass prompts, proceeding with the default action
|
||||||
|
# -c, --custom PATH Use PATH as the boilerplate template instead of
|
||||||
|
# $GITIGNORE_BOILERPLATE
|
||||||
# targets Comma- or space-separated list of language/tool names
|
# targets Comma- or space-separated list of language/tool names
|
||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# 0 Patterns appended, or resolved with -s/--stdout or -l/--list
|
# 0 Patterns appended, or resolved with -o/--stdout or -l/--list
|
||||||
# 1 Not in a git repository or API fetch failed
|
# 1 Not in a git repository or API fetch failed
|
||||||
#
|
#
|
||||||
# RETURNS
|
# RETURNS
|
||||||
# With -s/--stdout, the fetched .gitignore pattern text, printed to stdout.
|
# With -o/--stdout, the fetched .gitignore pattern text, printed to stdout.
|
||||||
# With -l/--list, the supported target list, printed to stdout.
|
# With -l/--list, the supported target list, printed to stdout.
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# gi python,venv
|
# gi python,venv
|
||||||
# gi -b -p
|
# gi -b -p
|
||||||
# gi -s node > .gitignore
|
# gi -o node > .gitignore
|
||||||
|
# gi -f # skip prompt, proceed with no patterns
|
||||||
|
# gi -c ~/my-template.gitignore
|
||||||
function gi --description 'Generate .gitignore files using the gitignore.io API'
|
function gi --description 'Generate .gitignore files using the gitignore.io API'
|
||||||
argparse h/help d/description l/list b/boilerplate p/prompt s/stdout -- $argv
|
argparse h/help d/description l/list b/boilerplate p/prompt o/stdout s/silent f/force c/custom= -- $argv
|
||||||
or return 1
|
or return 1
|
||||||
|
|
||||||
if set -q _flag_help
|
if set -q _flag_help
|
||||||
@@ -53,9 +65,18 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
echo " $c_flag-h, --help $c_reset Show this help message"
|
echo " $c_flag-h, --help $c_reset Show this help message"
|
||||||
echo " $c_flag-d, --description $c_reset Show the Fish function description"
|
echo " $c_flag-d, --description $c_reset Show the Fish function description"
|
||||||
echo " $c_flag-l, --list $c_reset List all supported targets from the API"
|
echo " $c_flag-l, --list $c_reset List all supported targets from the API"
|
||||||
echo " $c_flag-b, --boilerplate $c_reset Append boilerplate from $c_arg""\$GITIGNORE_BOILERPLATE$c_reset to .gitignore"
|
echo " $c_flag-b, --boilerplate $c_reset Append boilerplate (implied by "$c_flag"-c$c_reset)"
|
||||||
echo " $c_flag-p, --prompt $c_reset Prompt for patterns and append them to .gitignore"
|
echo " $c_flag-p, --prompt $c_reset Prompt for patterns and append them to .gitignore"
|
||||||
echo " $c_flag-s, --stdout $c_reset Print API output to stdout instead of appending to .gitignore"
|
echo " $c_flag-o, --stdout $c_reset Print generated content to stdout instead of .gitignore"
|
||||||
|
echo " $c_flag-s, --silent $c_reset Suppress progress output (errors and prompts still show)"
|
||||||
|
echo " $c_flag-f, --force $c_reset Bypass prompts, proceeding with the default action"
|
||||||
|
echo " $c_flag-c, --custom $c_reset $c_arg""PATH$c_reset Use PATH as the boilerplate template"
|
||||||
|
echo " $c_dim""instead of \$GITIGNORE_BOILERPLATE$c_reset"
|
||||||
|
echo ""
|
||||||
|
echo "$c_head""Boilerplate source (in priority order):$c_reset"
|
||||||
|
echo " 1. "$c_flag"-c/--custom$c_reset PATH, if given"
|
||||||
|
echo " 2. "$c_arg"\$GITIGNORE_BOILERPLATE$c_reset, if set"
|
||||||
|
echo " 3. "$c_dim"the bundled standard template$c_reset"
|
||||||
echo ""
|
echo ""
|
||||||
echo "$c_head""Examples:$c_reset"
|
echo "$c_head""Examples:$c_reset"
|
||||||
echo " $c_cmd""gi$c_reset $c_dim""# Append boilerplate and prompt for patterns (default)$c_reset"
|
echo " $c_cmd""gi$c_reset $c_dim""# Append boilerplate and prompt for patterns (default)$c_reset"
|
||||||
@@ -63,7 +84,9 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
echo " $c_cmd""gi -p$c_reset $c_dim""# Prompt for patterns and append to .gitignore$c_reset"
|
echo " $c_cmd""gi -p$c_reset $c_dim""# Prompt for patterns and append to .gitignore$c_reset"
|
||||||
echo " $c_cmd""gi$c_reset $c_arg""c++$c_reset $c_dim""# Append C++ patterns to .gitignore$c_reset"
|
echo " $c_cmd""gi$c_reset $c_arg""c++$c_reset $c_dim""# Append C++ patterns to .gitignore$c_reset"
|
||||||
echo " $c_cmd""gi$c_reset $c_arg""python,venv$c_reset $c_dim""# Append Python+venv patterns to .gitignore$c_reset"
|
echo " $c_cmd""gi$c_reset $c_arg""python,venv$c_reset $c_dim""# Append Python+venv patterns to .gitignore$c_reset"
|
||||||
echo " $c_cmd""gi -s$c_reset $c_arg""python,venv$c_reset $c_dim""# Print Python+venv patterns to stdout$c_reset"
|
echo " $c_cmd""gi -o$c_reset $c_arg""python,venv$c_reset $c_dim""# Print Python+venv patterns to stdout$c_reset"
|
||||||
|
echo " $c_cmd""gi -f$c_reset $c_dim""# Skip prompt, proceed with no patterns$c_reset"
|
||||||
|
echo " $c_cmd""gi -c$c_reset $c_arg""~/my.gitignore$c_reset $c_dim""# Append a custom boilerplate template$c_reset"
|
||||||
echo " $c_cmd""gi -l$c_reset | grep -i linux $c_dim""# Search for specific OS support$c_reset"
|
echo " $c_cmd""gi -l$c_reset | grep -i linux $c_dim""# Search for specific OS support$c_reset"
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
@@ -78,11 +101,14 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
set -l silent_flag 0
|
||||||
|
set -q _flag_silent; and set silent_flag 1
|
||||||
|
|
||||||
# Determine which modes to run
|
# Determine which modes to run
|
||||||
set -l do_boilerplate 0
|
set -l do_boilerplate 0
|
||||||
set -l do_prompt 0
|
set -l do_prompt 0
|
||||||
|
|
||||||
if set -q _flag_boilerplate
|
if set -q _flag_boilerplate; or set -q _flag_custom
|
||||||
set do_boilerplate 1
|
set do_boilerplate 1
|
||||||
end
|
end
|
||||||
if set -q _flag_prompt
|
if set -q _flag_prompt
|
||||||
@@ -95,15 +121,18 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
set do_prompt 1
|
set do_prompt 1
|
||||||
end
|
end
|
||||||
|
|
||||||
# Resolve git context for anything that writes to .gitignore
|
# Resolve git context for anything that writes to .gitignore.
|
||||||
|
# --stdout never touches .gitignore, so it never needs a git repo.
|
||||||
set -l gitignore_path ""
|
set -l gitignore_path ""
|
||||||
set -l readable_path ""
|
set -l readable_path ""
|
||||||
set -l needs_git 0
|
set -l needs_git 0
|
||||||
|
if not set -q _flag_stdout
|
||||||
if test $do_boilerplate -eq 1; or test $do_prompt -eq 1
|
if test $do_boilerplate -eq 1; or test $do_prompt -eq 1
|
||||||
set needs_git 1
|
set needs_git 1
|
||||||
else if set -q argv[1]; and not set -q _flag_stdout
|
else if set -q argv[1]
|
||||||
set needs_git 1
|
set needs_git 1
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if test $needs_git -eq 1
|
if test $needs_git -eq 1
|
||||||
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||||
@@ -116,39 +145,85 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
set readable_path (string replace -r "^$HOME" "~" $gitignore_path)
|
set readable_path (string replace -r "^$HOME" "~" $gitignore_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Boilerplate mode
|
# Boilerplate mode: resolve the template source, in priority order:
|
||||||
|
# 1. -c/--custom PATH
|
||||||
|
# 2. $GITIGNORE_BOILERPLATE
|
||||||
|
# 3. the bundled standard template (data/gi/boilerplate.gitignore)
|
||||||
if test $do_boilerplate -eq 1
|
if test $do_boilerplate -eq 1
|
||||||
if not set -q GITIGNORE_BOILERPLATE
|
set -l boilerplate_path ""
|
||||||
|
set -l boilerplate_ok 1
|
||||||
|
|
||||||
|
if set -q _flag_custom
|
||||||
|
if test -f "$_flag_custom"
|
||||||
|
set boilerplate_path "$_flag_custom"
|
||||||
|
else
|
||||||
set_color red --bold
|
set_color red --bold
|
||||||
echo "Error:" (set_color normal)"\$GITIGNORE_BOILERPLATE environment variable is not defined" >&2
|
echo "Error:" (set_color normal)"Custom boilerplate file not found at '$_flag_custom'" >&2
|
||||||
else if not test -f "$GITIGNORE_BOILERPLATE"
|
set boilerplate_ok 0
|
||||||
|
end
|
||||||
|
else if set -q GITIGNORE_BOILERPLATE
|
||||||
|
if test -f "$GITIGNORE_BOILERPLATE"
|
||||||
|
set boilerplate_path "$GITIGNORE_BOILERPLATE"
|
||||||
|
else
|
||||||
set_color red --bold
|
set_color red --bold
|
||||||
echo "Error:" (set_color normal)"Boilerplate file not found at '$GITIGNORE_BOILERPLATE'" >&2
|
echo "Error:" (set_color normal)"Boilerplate file not found at '$GITIGNORE_BOILERPLATE'" >&2
|
||||||
|
set boilerplate_ok 0
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if set -q __fish_config_dir
|
||||||
|
set boilerplate_path "$__fish_config_dir/data/gi/boilerplate.gitignore"
|
||||||
|
else
|
||||||
|
set boilerplate_path "$HOME/.config/fish/data/gi/boilerplate.gitignore"
|
||||||
|
end
|
||||||
|
if not test -f "$boilerplate_path"
|
||||||
|
set_color red --bold
|
||||||
|
echo "Error:" (set_color normal)"Bundled default boilerplate missing at '$boilerplate_path'" >&2
|
||||||
|
set boilerplate_ok 0
|
||||||
|
else if not set -q _flag_silent
|
||||||
|
set_color yellow --bold
|
||||||
|
echo "Notice:" (set_color normal)"\$GITIGNORE_BOILERPLATE not set; using the bundled default template."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if test $boilerplate_ok -eq 1
|
||||||
|
if set -q _flag_stdout
|
||||||
|
cat "$boilerplate_path"
|
||||||
else
|
else
|
||||||
set -l template_hash ""
|
set -l template_hash ""
|
||||||
if command -q md5sum
|
if command -q md5sum
|
||||||
set template_hash (md5sum "$GITIGNORE_BOILERPLATE" | string split ' ')[1]
|
set template_hash (md5sum "$boilerplate_path" | string split ' ')[1]
|
||||||
else if command -q md5
|
else if command -q md5
|
||||||
set template_hash (md5 -q "$GITIGNORE_BOILERPLATE")
|
set template_hash (md5 -q "$boilerplate_path")
|
||||||
end
|
end
|
||||||
|
|
||||||
set -l sig "# id: gitig-boilerplate-$template_hash"
|
set -l sig "# id: gitig-boilerplate-$template_hash"
|
||||||
|
|
||||||
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
|
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
|
||||||
|
if not set -q _flag_silent
|
||||||
set_color yellow --bold
|
set_color yellow --bold
|
||||||
echo "Notice:" (set_color normal)"Boilerplate already present in "(set_color cyan)"$readable_path"(set_color normal)"."
|
echo "Notice:" (set_color normal)"Boilerplate already present in "(set_color cyan)"$readable_path"(set_color normal)"."
|
||||||
|
end
|
||||||
else
|
else
|
||||||
printf "\n%s\n" "$sig" >>"$gitignore_path"
|
printf "\n%s\n" "$sig" >>"$gitignore_path"
|
||||||
cat "$GITIGNORE_BOILERPLATE" >>"$gitignore_path"
|
cat "$boilerplate_path" >>"$gitignore_path"
|
||||||
|
if not set -q _flag_silent
|
||||||
echo (set_color green)"✔"(set_color normal)" Appended boilerplate to "(set_color cyan)"$readable_path"(set_color normal)
|
echo (set_color green)"✔"(set_color normal)" Appended boilerplate to "(set_color cyan)"$readable_path"(set_color normal)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Prompt mode: ask for patterns, fetch and dedup each one individually
|
# Prompt mode: ask for patterns, fetch and dedup (or print) each one individually
|
||||||
if test $do_prompt -eq 1
|
if test $do_prompt -eq 1
|
||||||
|
set -l patterns ""
|
||||||
|
if set -q _flag_force
|
||||||
|
# Bypass the prompt: proceed with the default action (no patterns)
|
||||||
|
set patterns ""
|
||||||
|
else
|
||||||
read -P "Enter gitignore patterns (comma-separated, e.g. python,vim): " patterns
|
read -P "Enter gitignore patterns (comma-separated, e.g. python,vim): " patterns
|
||||||
or return 0
|
or return 0
|
||||||
|
end
|
||||||
set patterns (string trim -- $patterns)
|
set patterns (string trim -- $patterns)
|
||||||
if test -n "$patterns"
|
if test -n "$patterns"
|
||||||
for pattern in (string split "," -- $patterns)
|
for pattern in (string split "," -- $patterns)
|
||||||
@@ -159,11 +234,16 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
echo "Error: Failed to fetch gitignore for '$pattern'. Is the target spelled correctly?" >&2
|
echo "Error: Failed to fetch gitignore for '$pattern'. Is the target spelled correctly?" >&2
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
__gi_append_dedup "$content" "$pattern" "$gitignore_path" "$readable_path"
|
if set -q _flag_stdout
|
||||||
end
|
echo "$content"
|
||||||
else
|
else
|
||||||
|
__gi_append_dedup "$content" "$pattern" "$gitignore_path" "$readable_path" $silent_flag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else if not set -q _flag_silent
|
||||||
echo (set_color brblack)"No patterns selected. Skipping API fetch."(set_color normal)
|
echo (set_color brblack)"No patterns selected. Skipping API fetch."(set_color normal)
|
||||||
end
|
end
|
||||||
|
test $needs_git -eq 1; and gitignore-scrub
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -189,14 +269,19 @@ function gi --description 'Generate .gitignore files using the gitignore.io API'
|
|||||||
echo "Error: Failed to fetch gitignore for '$target'. Is the target spelled correctly?" >&2
|
echo "Error: Failed to fetch gitignore for '$target'. Is the target spelled correctly?" >&2
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
__gi_append_dedup "$content" "$target" "$gitignore_path" "$readable_path"
|
__gi_append_dedup "$content" "$target" "$gitignore_path" "$readable_path" $silent_flag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if test $needs_git -eq 1
|
||||||
|
gitignore-scrub
|
||||||
|
end
|
||||||
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# __gi_append_dedup <content> <label> <gitignore_path> <readable_path>
|
# __gi_append_dedup <content> <label> <gitignore_path> <readable_path> [silent]
|
||||||
#
|
#
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
# Appends gitignore content to a .gitignore file using MD5-based deduplication.
|
# Appends gitignore content to a .gitignore file using MD5-based deduplication.
|
||||||
@@ -207,14 +292,16 @@ end
|
|||||||
# label Human-readable label for the pattern set
|
# label Human-readable label for the pattern set
|
||||||
# gitignore_path Absolute path to the .gitignore file
|
# gitignore_path Absolute path to the .gitignore file
|
||||||
# readable_path Home-abbreviated path shown in output messages
|
# readable_path Home-abbreviated path shown in output messages
|
||||||
|
# silent 1 to suppress progress output, 0/omitted to show it
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# __gi_append_dedup "$content" "python" "$root/.gitignore" "~/.gitignore"
|
# __gi_append_dedup "$content" "python" "$root/.gitignore" "~/.gitignore" 0
|
||||||
function __gi_append_dedup
|
function __gi_append_dedup
|
||||||
set -l content $argv[1]
|
set -l content $argv[1]
|
||||||
set -l label $argv[2]
|
set -l label $argv[2]
|
||||||
set -l gitignore_path $argv[3]
|
set -l gitignore_path $argv[3]
|
||||||
set -l readable_path $argv[4]
|
set -l readable_path $argv[4]
|
||||||
|
set -l silent $argv[5]
|
||||||
|
|
||||||
set -l content_hash ""
|
set -l content_hash ""
|
||||||
if command -q md5sum
|
if command -q md5sum
|
||||||
@@ -226,10 +313,14 @@ function __gi_append_dedup
|
|||||||
set -l sig "# id: gi-patterns-$content_hash"
|
set -l sig "# id: gi-patterns-$content_hash"
|
||||||
|
|
||||||
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
|
if test -f "$gitignore_path"; and grep -qF "$sig" "$gitignore_path"
|
||||||
|
if test "$silent" != 1
|
||||||
set_color yellow --bold
|
set_color yellow --bold
|
||||||
echo "Notice:" (set_color normal)"$label patterns already present in "(set_color cyan)"$readable_path"(set_color normal)"."
|
echo "Notice:" (set_color normal)"$label patterns already present in "(set_color cyan)"$readable_path"(set_color normal)"."
|
||||||
|
end
|
||||||
else
|
else
|
||||||
printf "\n%s\n%s\n" "$sig" "$content" >>"$gitignore_path"
|
printf "\n%s\n%s\n" "$sig" "$content" >>"$gitignore_path"
|
||||||
|
if test "$silent" != 1
|
||||||
echo (set_color green)"✔"(set_color normal)" Appended $label patterns to "(set_color cyan)"$readable_path"(set_color normal)
|
echo (set_color green)"✔"(set_color normal)" Appended $label patterns to "(set_color cyan)"$readable_path"(set_color normal)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 10-network
|
# 10-network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 10-network
|
# 10-network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 10-network
|
# 10-network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 04-git-and-version-control
|
# 04-git-and-version-control
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Copyright (C) 2026 Rootiest
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# CATEGORY
|
||||||
|
# 04-git-and-version-control
|
||||||
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
|
# CLASSIFICATION
|
||||||
|
# blocking-prompt
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
# gitignore-scrub [-h] [-r] [-w | -f | -i]
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
# Finds files that are tracked by git but now match a .gitignore pattern
|
||||||
|
# (git ls-files -ci --exclude-standard) and offers to untrack them. In the
|
||||||
|
# default interactive mode, prompts once for all matches and runs
|
||||||
|
# git rm --cached on confirmation; a decline is remembered per-path in the
|
||||||
|
# repo's local git config (gitignore-scrub.skip) so the same file is not
|
||||||
|
# asked about again. -w/--warn is read-only: prints a Warning line per
|
||||||
|
# match and makes no changes, meant for non-interactive callers such as a
|
||||||
|
# git hook. -f/--force skips the prompt and untracks every match
|
||||||
|
# immediately. -i/--individual prompts once per file instead of once for
|
||||||
|
# the whole group. -w, -f, and -i are mutually exclusive. -r/--reset
|
||||||
|
# clears the repo's skip list first (combinable with any mode), so
|
||||||
|
# previously declined files are reconsidered. Silently does nothing on a
|
||||||
|
# repo with more tracked files than $GITIGNORE_SCRUB_LIMIT (default
|
||||||
|
# 5000), to avoid adding latency to huge repos.
|
||||||
|
#
|
||||||
|
# ARGUMENTS
|
||||||
|
# -h, --help Show help message
|
||||||
|
# -r, --reset Clear the remembered skip list before checking
|
||||||
|
# -w, --warn Read-only: print warnings instead of prompting, make no changes
|
||||||
|
# -f, --force Untrack every match immediately, no prompt
|
||||||
|
# -i, --individual Prompt once per file instead of once for the whole group
|
||||||
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 0 Clean, or a prompt/force run was handled
|
||||||
|
# 1 Not a git repository, or (-w only) unconfirmed matches were found
|
||||||
|
#
|
||||||
|
# EXAMPLE
|
||||||
|
# gitignore-scrub
|
||||||
|
# gitignore-scrub --warn
|
||||||
|
# gitignore-scrub --force
|
||||||
|
# gitignore-scrub --individual
|
||||||
|
# gitignore-scrub --reset
|
||||||
|
function gitignore-scrub --description 'Find and optionally untrack files newly matched by .gitignore'
|
||||||
|
argparse --exclusive w,f,i h/help r/reset w/warn f/force i/individual -- $argv
|
||||||
|
or return 1
|
||||||
|
|
||||||
|
if set -q _flag_help
|
||||||
|
__fish_palette
|
||||||
|
echo "$c_head""Usage:$c_reset $c_cmd""gitignore-scrub$c_reset $c_arg""[FLAGS]$c_reset"
|
||||||
|
echo ""
|
||||||
|
echo "$c_head""Flags:$c_reset"
|
||||||
|
echo " $c_flag-h, --help$c_reset Show this help message"
|
||||||
|
echo " $c_flag-r, --reset$c_reset Clear the remembered skip list before checking"
|
||||||
|
echo " $c_flag-w, --warn$c_reset Read-only: print warnings instead of prompting"
|
||||||
|
echo " $c_flag-f, --force$c_reset Untrack every match immediately, no prompt"
|
||||||
|
echo " $c_flag-i, --individual$c_reset Prompt once per file instead of once for the group"
|
||||||
|
echo ""
|
||||||
|
echo "$c_head""Examples:$c_reset"
|
||||||
|
echo " $c_cmd""gitignore-scrub$c_reset $c_dim""# Interactive: prompt to untrack matches$c_reset"
|
||||||
|
echo " $c_cmd""gitignore-scrub --warn$c_reset $c_dim""# Read-only: for use in a git hook$c_reset"
|
||||||
|
echo " $c_cmd""gitignore-scrub --force$c_reset $c_dim""# Untrack every match, no prompt$c_reset"
|
||||||
|
echo " $c_cmd""gitignore-scrub --individual$c_reset $c_dim""# Prompt per file instead of as a group$c_reset"
|
||||||
|
echo " $c_cmd""gitignore-scrub --reset$c_reset $c_dim""# Reconsider previously declined files$c_reset"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if not git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||||
|
set_color red --bold
|
||||||
|
echo "Error:" (set_color normal)"Not a git repository (or any parent directories)" >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l limit 5000
|
||||||
|
set -q GITIGNORE_SCRUB_LIMIT; and set limit $GITIGNORE_SCRUB_LIMIT
|
||||||
|
|
||||||
|
set -l tracked_count (git ls-files | count)
|
||||||
|
if test $tracked_count -gt $limit
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l offenders (git ls-files -ci --exclude-standard)
|
||||||
|
set -q offenders[1]; or return 0
|
||||||
|
|
||||||
|
if set -q _flag_reset
|
||||||
|
git config --local --remove-section gitignore-scrub 2>/dev/null
|
||||||
|
end
|
||||||
|
|
||||||
|
set -l skip_list (git config --local --get-all gitignore-scrub.skip 2>/dev/null)
|
||||||
|
set -l pending
|
||||||
|
for f in $offenders
|
||||||
|
contains -- "$f" $skip_list; or set -a pending $f
|
||||||
|
end
|
||||||
|
set -q pending[1]; or return 0
|
||||||
|
|
||||||
|
if set -q _flag_warn
|
||||||
|
for f in $pending
|
||||||
|
set_color yellow --bold
|
||||||
|
echo -n "Warning: "
|
||||||
|
set_color normal
|
||||||
|
echo "$f is tracked but ignored"
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if set -q _flag_force
|
||||||
|
git rm --cached -- $pending >/dev/null
|
||||||
|
echo (set_color green)"✔"(set_color normal)" Untracked "(count $pending)" file(s)."
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if set -q _flag_individual
|
||||||
|
for f in $pending
|
||||||
|
read -P "Remove '$f' from git tracking? [y/N] " confirm
|
||||||
|
if string match -qir '^y' -- "$confirm"
|
||||||
|
git rm --cached -- "$f" >/dev/null
|
||||||
|
echo (set_color green)"✔"(set_color normal)" Untracked $f"
|
||||||
|
else
|
||||||
|
git config --local --add gitignore-scrub.skip "$f"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
set_color yellow
|
||||||
|
echo (count $pending)" tracked file(s) now match .gitignore:"(set_color normal)
|
||||||
|
for f in $pending
|
||||||
|
echo " $f"
|
||||||
|
end
|
||||||
|
read -P "Remove from git tracking? [y/N] " confirm
|
||||||
|
if string match -qir '^y' -- "$confirm"
|
||||||
|
git rm --cached -- $pending >/dev/null
|
||||||
|
echo (set_color green)"✔"(set_color normal)" Untracked "(count $pending)" file(s)."
|
||||||
|
else
|
||||||
|
for f in $pending
|
||||||
|
git config --local --add gitignore-scrub.skip "$f"
|
||||||
|
end
|
||||||
|
echo (set_color brblack)"Remembered — won't ask again for these files."(set_color normal)
|
||||||
|
end
|
||||||
|
end
|
||||||
+12
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 04-git-and-version-control
|
# 04-git-and-version-control
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# gitui
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# gitui [args...]
|
# gitui [args...]
|
||||||
#
|
#
|
||||||
@@ -14,9 +17,17 @@
|
|||||||
# ARGUMENTS
|
# ARGUMENTS
|
||||||
# args... Arguments forwarded to the gitui command
|
# args... Arguments forwarded to the gitui command
|
||||||
#
|
#
|
||||||
|
# EXIT STATUS
|
||||||
|
# 1 gitui is not installed
|
||||||
|
# * Exit status of gitui otherwise
|
||||||
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# gitui
|
# gitui
|
||||||
function gitui --wraps='gitui' --description 'alias gitui=gitui -t mocha.ron'
|
function gitui --wraps='gitui' --description 'alias gitui=gitui -t mocha.ron'
|
||||||
command gitui -t frappe.ron $argv
|
if not type -q -f gitui
|
||||||
|
echo (set_color red)"Error: gitui is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
command gitui -t frappe.ron $argv
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 04-git-and-version-control
|
# 04-git-and-version-control
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# git
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# joplin
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# joplin [args...]
|
# joplin [args...]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# logging/terminal-capture
|
# logging/terminal-capture
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitty, __kitty_logging_dir, __kitty_logging_has_watcher,
|
||||||
|
# __kitty_logging_version
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(grep,mkdir,rm)
|
# bypasses-shadow(grep,mkdir,rm)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# docker, lazydocker
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# ld
|
# ld
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/shell-tools
|
# aliases/shell-tools
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# ov, more
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(cat,less)
|
# bypasses-shadow(cat,less)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 07-system-and-monitoring
|
# 07-system-and-monitoring
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# sbctl
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# limine-edit
|
# limine-edit
|
||||||
#
|
#
|
||||||
|
|||||||
+10
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 07-system-and-monitoring
|
# 07-system-and-monitoring
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# loginctl
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# lock
|
# lock
|
||||||
#
|
#
|
||||||
@@ -11,12 +14,18 @@
|
|||||||
# Locks the current desktop session using loginctl lock-session.
|
# Locks the current desktop session using loginctl lock-session.
|
||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# Exit status of loginctl lock-session
|
# 1 loginctl is not installed
|
||||||
|
# * Exit status of loginctl lock-session otherwise
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# lock
|
# lock
|
||||||
function lock --wraps='loginctl' --description 'alias lock=loginctl'
|
function lock --wraps='loginctl' --description 'alias lock=loginctl'
|
||||||
__fish_help_header (status current-function) $argv; and return 0
|
__fish_help_header (status current-function) $argv; and return 0
|
||||||
|
|
||||||
|
if not type -q loginctl
|
||||||
|
echo (set_color red)"Error: loginctl is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
loginctl lock-session
|
loginctl lock-session
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# integrations/history-logs
|
# integrations/history-logs
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _scrollback_prune_junk, fzf, nvim, ov
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(cat), self-limiting(rm), network
|
# bypasses-shadow(cat), self-limiting(rm), network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/filesystem
|
# aliases/filesystem
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# eza, lsd
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ls)
|
# bypasses-shadow(ls)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fish_mkdir_p
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(cd)
|
# bypasses-shadow(cd)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/filesystem
|
# aliases/filesystem
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fish_mkdir_p
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(mkdir)
|
# bypasses-shadow(mkdir)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# xdg-mime, xdg-open
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# open-url [-s|--silent] [-v|--verbose] <url>
|
# open-url [-s|--silent] [-v|--verbose] <url>
|
||||||
# open-url --help
|
# open-url --help
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 05-package-management
|
# 05-package-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fzf, pacman, paru, yay
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/network
|
# aliases/network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# prettyping
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ping)
|
# bypasses-shadow(ping)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 05-package-management
|
# 05-package-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fish_deps_detect_pm, pacman, paru, yay
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 13-media-and-utilities
|
# 13-media-and-utilities
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fzf_preview_media, _fzf_wrapper, fd, fdfind, file, xdg-mime, mpv, vlc
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# play-media [-p|--player <cmd>]
|
# play-media [-p|--player <cmd>]
|
||||||
# play-media --help
|
# play-media --help
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# _fish_mkdir_p
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# poke <file> [file...]
|
# poke <file> [file...]
|
||||||
#
|
#
|
||||||
|
|||||||
+10
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 07-system-and-monitoring
|
# 07-system-and-monitoring
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# lsof
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# ports
|
# ports
|
||||||
#
|
#
|
||||||
@@ -12,12 +15,18 @@
|
|||||||
# port numbers and addresses without hostname resolution.
|
# port numbers and addresses without hostname resolution.
|
||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# Exit status of lsof
|
# 1 lsof is not installed
|
||||||
|
# * Exit status of lsof otherwise
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# ports
|
# ports
|
||||||
function ports --wraps='sudo' --description 'Show active network listeners'
|
function ports --wraps='sudo' --description 'Show active network listeners'
|
||||||
__fish_help_header (status current-function) $argv; and return 0
|
__fish_help_header (status current-function) $argv; and return 0
|
||||||
|
|
||||||
|
if not type -q lsof
|
||||||
|
echo (set_color red)"Error: lsof is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
sudo lsof -iTCP -sTCP:LISTEN -P -n
|
sudo lsof -iTCP -sTCP:LISTEN -P -n
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 12-ai-and-developer-tools
|
# 12-ai-and-developer-tools
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# aichat
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# qc [prompt...]
|
# qc [prompt...]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 10-network
|
# 10-network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# curl, qrencode
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(cat), network
|
# self-limiting(cat), network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# shuf
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(cat)
|
# bypasses-shadow(cat)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 03-editors-and-viewers
|
# 03-editors-and-viewers
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fish
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# rawfish [args...]
|
# rawfish [args...]
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/filesystem
|
# aliases/filesystem
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# trash
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(rm), destructive
|
# bypasses-shadow(rm), destructive
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 07-system-and-monitoring
|
# 07-system-and-monitoring
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# sbctl
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(grep)
|
# self-limiting(grep)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 07-system-and-monitoring
|
# 07-system-and-monitoring
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# busctl
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# screensleep
|
# screensleep
|
||||||
#
|
#
|
||||||
@@ -12,13 +15,19 @@
|
|||||||
# PowerDevil "Turn Off Screen" global shortcut via busctl.
|
# PowerDevil "Turn Off Screen" global shortcut via busctl.
|
||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# Exit status of busctl
|
# 1 busctl is not installed
|
||||||
|
# * Exit status of busctl otherwise
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# screensleep
|
# screensleep
|
||||||
function screensleep --description 'Turn off the display using KDE PowerDevil'
|
function screensleep --description 'Turn off the display using KDE PowerDevil'
|
||||||
__fish_help_header (status current-function) $argv; and return 0
|
__fish_help_header (status current-function) $argv; and return 0
|
||||||
|
|
||||||
|
if not type -q busctl
|
||||||
|
echo (set_color red)"Error: busctl is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
# Optional: 1-second delay to ensure no keystrokes wake it immediately
|
# Optional: 1-second delay to ensure no keystrokes wake it immediately
|
||||||
sleep 1
|
sleep 1
|
||||||
busctl --user call \
|
busctl --user call \
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 01-file-and-directory
|
# 01-file-and-directory
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# fd, trash
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(rm), bypasses-shadow(rm), destructive
|
# uses-shadow(rm), bypasses-shadow(rm), destructive
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 05-package-management
|
# 05-package-management
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# paru, yay
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
# site exit-plain: overrides/key-bindings
|
# site exit-plain: overrides/key-bindings
|
||||||
# site logging-guard: logging/terminal-capture
|
# site logging-guard: logging/terminal-capture
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitty, ps, _scrollback_prune_junk
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# self-limiting(rm,mkdir), destructive
|
# self-limiting(rm,mkdir), destructive
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# integrations/window-mgmt
|
# integrations/window-mgmt
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitty, wezterm
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# split [-h | -v] [command...]
|
# split [-h | -v] [command...]
|
||||||
#
|
#
|
||||||
@@ -52,6 +55,17 @@ function split --description 'Run a command in a new terminal split'
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# $TERM/$TERM_PROGRAM only prove the terminal type, not that its CLI
|
||||||
|
# binary is on $PATH -- e.g. sshing out from Kitty/WezTerm inherits the
|
||||||
|
# env var on the remote host without the binary. Check explicitly.
|
||||||
|
if test $is_kitty -eq 1; and not type -q kitty
|
||||||
|
echo "Error: 'split' detected Kitty but the kitty binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
else if test $is_wezterm -eq 1; and not type -q wezterm
|
||||||
|
echo "Error: 'split' detected WezTerm but the wezterm binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
set -l kitty_loc hsplit
|
set -l kitty_loc hsplit
|
||||||
set -l wez_loc --bottom
|
set -l wez_loc --bottom
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# integrations/window-mgmt
|
# integrations/window-mgmt
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitty, wezterm
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# spwin [args...]
|
# spwin [args...]
|
||||||
#
|
#
|
||||||
@@ -33,13 +36,23 @@ function spwin --wraps='~/.config/kitty/spawn-window.sh' --description 'spawn wi
|
|||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# $TERM/$TERM_PROGRAM only prove the terminal type, not that its CLI
|
||||||
|
# binary is on $PATH -- e.g. sshing out from Kitty/WezTerm inherits the
|
||||||
|
# env var on the remote host without the binary. Check explicitly.
|
||||||
if test "$TERM" = xterm-kitty
|
if test "$TERM" = xterm-kitty
|
||||||
if test -x ~/.config/kitty/spawn-window.sh
|
if test -x ~/.config/kitty/spawn-window.sh
|
||||||
~/.config/kitty/spawn-window.sh $argv
|
~/.config/kitty/spawn-window.sh $argv
|
||||||
else
|
else if type -q kitty
|
||||||
kitty @ launch --type=window $argv
|
kitty @ launch --type=window $argv
|
||||||
|
else
|
||||||
|
echo "Error: 'spwin' detected Kitty but neither spawn-window.sh nor the kitty binary is available." >&2
|
||||||
|
return 1
|
||||||
end
|
end
|
||||||
else if test "$TERM_PROGRAM" = WezTerm
|
else if test "$TERM_PROGRAM" = WezTerm
|
||||||
|
if not type -q wezterm
|
||||||
|
echo "Error: 'spwin' detected WezTerm but the wezterm binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
wezterm cli spawn $argv
|
wezterm cli spawn $argv
|
||||||
else
|
else
|
||||||
echo "Error: The 'spwin' command requires Kitty or WezTerm." >&2
|
echo "Error: The 'spwin' command requires Kitty or WezTerm." >&2
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/network
|
# aliases/network
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitten
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(ssh), network
|
# bypasses-shadow(ssh), network
|
||||||
#
|
#
|
||||||
|
|||||||
+12
-1
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 13-media-and-utilities
|
# 13-media-and-utilities
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# systemd-inhibit, steam
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# steam-dl
|
# steam-dl
|
||||||
#
|
#
|
||||||
@@ -12,13 +15,21 @@
|
|||||||
# or sleeping during active downloads.
|
# or sleeping during active downloads.
|
||||||
#
|
#
|
||||||
# EXIT STATUS
|
# EXIT STATUS
|
||||||
# Exit status of steam (via systemd-inhibit)
|
# 1 systemd-inhibit or steam is not installed
|
||||||
|
# * Exit status of steam (via systemd-inhibit) otherwise
|
||||||
#
|
#
|
||||||
# EXAMPLE
|
# EXAMPLE
|
||||||
# steam-dl
|
# steam-dl
|
||||||
function steam-dl --description 'Run Steam while inhibiting system sleep'
|
function steam-dl --description 'Run Steam while inhibiting system sleep'
|
||||||
__fish_help_header (status current-function) $argv; and return 0
|
__fish_help_header (status current-function) $argv; and return 0
|
||||||
|
|
||||||
|
for cmd in systemd-inhibit steam
|
||||||
|
if not type -q $cmd
|
||||||
|
echo (set_color red)"Error: $cmd is not installed."(set_color normal) >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
echo "Inhibiting sleep while Steam downloads..."
|
echo "Inhibiting sleep while Steam downloads..."
|
||||||
systemd-inhibit --why="Active Download" --who="User" --what=idle:sleep steam
|
systemd-inhibit --why="Active Download" --who="User" --what=idle:sleep steam
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 12-ai-and-developer-tools
|
# 12-ai-and-developer-tools
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# agy
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(claude)
|
# uses-shadow(claude)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# integrations/window-mgmt
|
# integrations/window-mgmt
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# kitty, wezterm, konsole
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# tab [args...]
|
# tab [args...]
|
||||||
#
|
#
|
||||||
@@ -39,11 +42,27 @@ function tab --description 'Spawn a new tab in the current terminal'
|
|||||||
set dir "$PWD"
|
set dir "$PWD"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# $TERM/$TERM_PROGRAM/$KONSOLE_VERSION only prove the terminal type,
|
||||||
|
# not that its CLI binary is on $PATH -- e.g. sshing out from one of
|
||||||
|
# these inherits the env var on the remote host without the binary.
|
||||||
|
# Check explicitly.
|
||||||
if test "$TERM" = xterm-kitty
|
if test "$TERM" = xterm-kitty
|
||||||
|
if not type -q kitty
|
||||||
|
echo "Error: 'tab' detected Kitty but the kitty binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
kitty @ launch --type=tab --cwd="$dir" $argv
|
kitty @ launch --type=tab --cwd="$dir" $argv
|
||||||
else if test "$TERM_PROGRAM" = WezTerm
|
else if test "$TERM_PROGRAM" = WezTerm
|
||||||
|
if not type -q wezterm
|
||||||
|
echo "Error: 'tab' detected WezTerm but the wezterm binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
wezterm cli spawn --cwd "$dir" $argv
|
wezterm cli spawn --cwd "$dir" $argv
|
||||||
else if set -q KONSOLE_VERSION
|
else if set -q KONSOLE_VERSION
|
||||||
|
if not type -q konsole
|
||||||
|
echo "Error: 'tab' detected Konsole but the konsole binary is not installed." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
konsole --new-tab --workdir "$dir" $argv
|
konsole --new-tab --workdir "$dir" $argv
|
||||||
else
|
else
|
||||||
echo "Error: No supported terminal found. Try Kitty, WezTerm, or Konsole." >&2
|
echo "Error: No supported terminal found. Try Kitty, WezTerm, or Konsole." >&2
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 14-miscellaneous
|
# 14-miscellaneous
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# tmux
|
||||||
|
#
|
||||||
# SYNOPSIS
|
# SYNOPSIS
|
||||||
# tmux-clean
|
# tmux-clean
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# aliases/monitor
|
# aliases/monitor
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# btop
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# bypasses-shadow(top)
|
# bypasses-shadow(top)
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
# COMPONENT
|
# COMPONENT
|
||||||
# integrations/pkg-upgrade
|
# integrations/pkg-upgrade
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# paru, yay
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# network
|
# network
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
# CATEGORY
|
# CATEGORY
|
||||||
# 03-editors-and-viewers
|
# 03-editors-and-viewers
|
||||||
#
|
#
|
||||||
|
# DEPENDENCIES
|
||||||
|
# nvim
|
||||||
|
#
|
||||||
# CLASSIFICATION
|
# CLASSIFICATION
|
||||||
# uses-shadow(less)
|
# uses-shadow(less)
|
||||||
#
|
#
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user