Initial import: chezmoi source for CachyOS + Umbriel + Noctalia
Imported from prdlk/dotfiles master (efdd827). Drops the niri, Hyprland/Omarchy, macOS, and Blume docs-site layers; docs/ is plain Markdown.
This commit is contained in:
commit
ee355bcfd5
152 files changed
+11564
No files matched your search
@@ -0,0 +1,75 @@
|
||||
# dots
|
||||
|
||||
prdlk's CachyOS dotfiles, managed with chezmoi. One command installs chezmoi and applies the whole setup.
|
||||
|
||||
```sh
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply https://prad.codes/env/cachyos.git
|
||||
```
|
||||
|
||||
That command installs chezmoi, clones [env/cachyos](https://prad.codes/env/cachyos) together with its submodules, asks three questions (name, email, machine role), installs packages, and applies every config. See [Installation](installation.md) for the prerequisites, the age key, and the options.
|
||||
|
||||
This is the CachyOS variant: CachyOS (Arch) with the Umbriel compositor and the Noctalia shell.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Installation](installation.md)
|
||||
- [How it works](chezmoi/README.md)
|
||||
- [Config and roles](chezmoi/config.md)
|
||||
- [Packages and aliases](chezmoi/data.md)
|
||||
- [Apply scripts](chezmoi/scripts.md)
|
||||
- [Externals and ignore rules](chezmoi/externals-and-ignore.md)
|
||||
- [Shell](shell/README.md)
|
||||
- [fish](shell/fish.md)
|
||||
- [bash and zsh](shell/bash-zsh.md)
|
||||
- [Aliases](shell/aliases.md)
|
||||
- [Scripts](shell/scripts.md)
|
||||
- [Starship](shell/starship.md)
|
||||
- [atuin](shell/atuin.md)
|
||||
- [Terminal](terminal/README.md)
|
||||
- [kitty](terminal/kitty.md)
|
||||
- [Ghostty](terminal/ghostty.md)
|
||||
- [tmux](terminal/tmux.md)
|
||||
- [herdr](terminal/herdr.md)
|
||||
- [Tools](tools/README.md)
|
||||
- [Git](tools/git.md)
|
||||
- [GitHub CLI](tools/github-cli.md)
|
||||
- [Neovim](tools/neovim.md)
|
||||
- [Yazi](tools/yazi.md)
|
||||
- [Lazygit](tools/lazygit.md)
|
||||
- [Lazydocker](tools/lazydocker.md)
|
||||
- [bat](tools/bat.md)
|
||||
- [television](tools/television.md)
|
||||
- [Topgrade](tools/topgrade.md)
|
||||
- [Hunk](tools/hunk.md)
|
||||
- [drydock](tools/drydock.md)
|
||||
- [LeetCode](tools/leetcode.md)
|
||||
- [Desktop](desktop/README.md)
|
||||
- [Umbriel](desktop/umbriel.md)
|
||||
- [Fonts and default apps](desktop/fonts-and-apps.md)
|
||||
- [AI agents](ai/README.md)
|
||||
- [Skills](ai/skills.md)
|
||||
|
||||
## What's managed
|
||||
|
||||
- [How it works](chezmoi/README.md): chezmoi layout, machine roles, symlink mode, age encryption, package manifests, apply scripts, and externals.
|
||||
- [Shell](shell/README.md): fish, bash, and zsh with shared aliases, starship, atuin, and the `~/.local/bin` scripts.
|
||||
- [Terminal](terminal/README.md): kitty, ghostty, tmux, and herdr.
|
||||
- [Tools](tools/README.md): git, gh and gh-dash, neovim, yazi, lazygit, lazydocker, bat, topgrade, hunk, and more.
|
||||
- [Desktop](desktop/README.md): Umbriel with Noctalia on CachyOS, fonts, default apps, and the eGPU notes.
|
||||
- [AI agents](ai/README.md): Claude Code, omp, and the shared agent skill library.
|
||||
|
||||
## Machine roles
|
||||
|
||||
The first apply asks for a role. It controls which parts of the repo land on the machine.
|
||||
|
||||
| Role | Platform | Gets |
|
||||
| --- | --- | --- |
|
||||
| `desktop` | CachyOS / Arch Linux | Everything: universal toolset plus the Linux desktop layer (Umbriel, Noctalia) |
|
||||
| `laptop` | CachyOS / Arch Linux | Same as `desktop` |
|
||||
| `server` | Any | Universal toolset only |
|
||||
|
||||
The default is `desktop`. See [Configuration](chezmoi/config.md) and [Ignore rules](chezmoi/externals-and-ignore.md).
|
||||
|
||||
## Theme
|
||||
|
||||
The terminal and agent palette is **CherryBlossom**, the custom Noctalia palette in [prdlk/noctalia](https://github.com/prdlk/noctalia) (`palettes/CherryBlossom.json`). Ghostty selects its `cherry-blossom` theme, tmux colors its tab bar, popups, and command palette with it, atuin selects `cherry-blossom`, television selects `cherry-blossom-transparent` (no background, so the terminal's shows through), lazygit and lazydocker use its border and selection colors, and omp selects its `cherry-blossom` dark theme. nvim, bat, and yazi still select **cyberdream**; fish ships a cyberdream theme but does not select it. kitty's colors come from the shared kitty external. The koda and ii-auto variants are also shipped for ghostty, kitty, and yazi. The font is **Victor Mono Nerd Font**.
|
||||
@@ -0,0 +1,20 @@
|
||||
# AI agents
|
||||
|
||||
The shared agent skill library and the shell entry points for coding agents.
|
||||
|
||||
The repo manages the agent skill library only. Claude Code (`~/.claude`) and omp (`~/.omp`) configs are not in this repo. omp, opencode, and crush are global mise tools (`packages.shared.mise`, installed by [15-tools](../chezmoi/scripts.md#15-tools)) and are updated by topgrade's `mise upgrade`; no other agent CLI is installed by chezmoi.
|
||||
|
||||
- [Skills](skills.md): Agent skills declared in `.chezmoidata/skills.yaml` and installed into `~/.agents/skills` and `~/.claude/skills` by an apply script.
|
||||
|
||||
## Pages
|
||||
|
||||
| Page | Source in repo | Target in `$HOME` | Covers |
|
||||
| --- | --- | --- | --- |
|
||||
| [Skills](skills.md) | `.chezmoidata/skills.yaml`, `.chezmoiscripts/run_onchange_after_25-skills.sh.tmpl` | `~/.agents/skills/`, `~/.claude/skills/` | Skill list and sources; installed by the Skills CLI, not vendored |
|
||||
|
||||
## Shell entry points
|
||||
|
||||
- `ai` (fish function) opens a `gum choose` picker over `claude`, `opencode`, `codex`, `hermes`, and `fractal`. Picking `claude` runs `claude --dangerously-skip-permissions`; the others run the binary of the same name. See [fish](../shell/fish.md).
|
||||
- <kbd>Ctrl</kbd> <kbd>A</kbd> runs [`omp-session`](../shell/scripts.md#omp-session) from the fish and zsh prompt: on the default branch it resumes a session written in the last 3 hours, else opens the `omp-sessions` television cable; on any other branch it resumes that branch's newest session or starts a new one. `tv agents` still lists every installed agent CLI ([television](../tools/television.md#agents)).
|
||||
- <kbd>Ctrl</kbd> <kbd>N</kbd> starts a new omp session from the fish and zsh prompt, skipping every resume rule. Inside the `omp-sessions` cable, <kbd>ctrl-n</kbd> does the same instead of resuming the selected session.
|
||||
- `launch_omp_livediff_overlay` and `lc-elir` are omp helpers in `~/.local/bin`; see [scripts](../shell/scripts.md).
|
||||
@@ -0,0 +1,200 @@
|
||||
# Skills
|
||||
|
||||
Agent skills declared in .chezmoidata/skills.yaml and installed into ~/.agents/skills and ~/.claude/skills by a chezmoi script.
|
||||
|
||||
Agent skills are folders with a `SKILL.md` file (frontmatter `name` and `description`, then instructions) that agents load on demand. The repo does not vendor them. It declares each skill and its source in `.chezmoidata/skills.yaml`, and the `run_onchange_after_25-skills.sh.tmpl` script installs them with the Skills CLI (`npx skills`) on `chezmoi apply`.
|
||||
|
||||
## Files
|
||||
|
||||
| Source in repo | Target | Notes |
|
||||
| --- | --- | --- |
|
||||
| `.chezmoidata/skills.yaml` | chezmoi data `.skills` | CLI version, default agents, and one entry per source |
|
||||
| `.chezmoiscripts/run_onchange_after_25-skills.sh.tmpl` | runs `skills add` / `skills remove` | Re-runs when `.skills` changes; see [Scripts](../chezmoi/scripts.md#25-skills) |
|
||||
|
||||
Not role-gated; the sync runs on every machine. Installed skill files are machine state, not repo files:
|
||||
|
||||
| Path | Written by | Contents |
|
||||
| --- | --- | --- |
|
||||
| `~/.agents/skills/<name>/` | Skills CLI | Shared library for `universal` installs, read by Codex, Gemini CLI, OpenCode, Zed, … |
|
||||
| `~/.claude/skills/<name>` | Skills CLI | Relative symlink `../../.agents/skills/<name>` for universal installs; a real copied directory for Claude-only installs |
|
||||
| `$XDG_STATE_HOME/skills/.skill-lock.json` | Skills CLI | The CLI's own install record (`~/.agents/.skill-lock.json` when `XDG_STATE_HOME` is unset) |
|
||||
| `${XDG_STATE_HOME:-~/.local/state}/chezmoi/skills.txt` | sync script | Skill names installed by the previous sync, used to remove undeclared ones |
|
||||
|
||||
## skills.yaml
|
||||
|
||||
```yaml
|
||||
skills:
|
||||
cli: skills@1.7.0 # pinned CLI, run via npx -y
|
||||
agents: [universal, claude-code] # default install targets
|
||||
sources:
|
||||
- source: cloudflare/skills
|
||||
agents: [claude-code] # Claude Code only
|
||||
skills: [agents-sdk, durable-objects]
|
||||
```
|
||||
|
||||
| Key | Meaning |
|
||||
| --- | --- |
|
||||
| `cli` | npm spec of the Skills CLI, run as `npx -y <cli>` |
|
||||
| `agents` | Default `-a` targets. `universal` is `~/.agents/skills`; `claude-code` is `~/.claude/skills` |
|
||||
| `sources[].source` | Anything `skills add` accepts: `owner/repo` (default branch), a GitHub tree URL (pins a ref or subdirectory), or a well-known endpoint URL |
|
||||
| `sources[].skills` | Skill names to install from that source (`--skill`) |
|
||||
| `sources[].agents` | Optional override of the default `agents` |
|
||||
|
||||
108 skills across 42 source entries. `cloudflare/skills` appears twice: once with the defaults, once with `agents: [claude-code]`.
|
||||
|
||||
### Pinned sources
|
||||
|
||||
Unpinned sources track the upstream default branch, so skill content updates whenever the sync runs. Skills that upstream deleted or renamed are pinned to the last ref that still has them:
|
||||
|
||||
| Source | Skills | Reason |
|
||||
| --- | --- | --- |
|
||||
| `https://github.com/mattpocock/skills/tree/v1.1.0` | `design-an-interface`, `obsidian-vault`, `qa`, `request-refactor-plan`, `ubiquitous-language`, `writing-great-skills` | Removed upstream in c66bdee (`writing-great-skills` became `writing-for-agents`) |
|
||||
| `https://github.com/mattpocock/skills/tree/153fc1b…` | `resolving-merge-conflicts` | Removed upstream in daa01d8 |
|
||||
| `https://github.com/terrylica/cc-skills/tree/6d35f84…` | `mise-tasks` | Retired upstream in dd0d3f2f |
|
||||
| `https://github.com/cloudflare/skills/tree/30553f8…` | `sandbox-sdk` | Replaced upstream by `sandbox-stable` / `sandbox-next` in f96bff7 |
|
||||
|
||||
`create-beads-orchestration` uses the subdirectory URL `https://github.com/AvivK5498/Claude-Code-Beads-Orchestration/tree/main/skills/create-beads-orchestration`, because the bare repo's root `SKILL.md` would install the whole repository as the skill. The Mintlify skills come from the well-known endpoint `https://mintlify.com/docs`.
|
||||
|
||||
### Not managed
|
||||
|
||||
`terminal-browser` and similar skills can be installed by other packages. They are not in `skills.yaml`, and the sync never touches them.
|
||||
|
||||
## Managing skills
|
||||
|
||||
Find a skill's source and name with the CLI, then declare it:
|
||||
|
||||
```sh
|
||||
npx skills find <query> # search the registry
|
||||
npx skills add <source> --list # list the skills a source provides
|
||||
```
|
||||
|
||||
- **Add**: add the name under its source in `.chezmoidata/skills.yaml` (or a new `source` entry) and run `chezmoi apply`.
|
||||
- **Remove**: delete the name and run `chezmoi apply`; the sync runs `skills remove` for it.
|
||||
- **Claude Code only**: put it in an entry with `agents: [claude-code]`.
|
||||
|
||||
Do not `npx skills add` by hand and expect chezmoi to track it; only `skills.yaml` is the record. The script only re-runs when `.skills` changes, so unpinned sources pick up upstream updates on the next sync after an edit to `skills.yaml`.
|
||||
|
||||
## How the sync works
|
||||
|
||||
1. **Trigger**: The script is `run_onchange_after_`: chezmoi re-runs it after applying files whenever the sha256 of `.skills` (as JSON) changes. If `npx` is missing it warns and skips.
|
||||
2. **Migrate**: One-time cleanup of the old vendored layout: deletes any directory in `~/.agents/skills` or `~/.claude/skills` that contains symlinks into the chezmoi source directory, and removes `~/.agents/.skill-lock.json` if it is a symlink into the source directory.
|
||||
3. **Remove**: Skills listed in `skills.txt` (the previous sync) but no longer declared are removed with `skills remove -g -y`.
|
||||
4. **Install**: One `skills add <source> -g -y -a <agents> --skill <names…>` per source entry. Output is captured; on failure the last 8 lines are printed.
|
||||
5. **Record**: The declared names are written to `skills.txt`. Claude-only (`-a claude-code`) installs are not recorded in the CLI lock, which is why the script keeps its own state file.
|
||||
6. **Retry**: If any source failed the script exits 1, so chezmoi keeps the old hash and runs it again on the next apply.
|
||||
|
||||
Every universal skill gets both a `~/.agents/skills` directory and a `~/.claude/skills` link. Claude Code resolves `~/.claude/skills/<name>` → `../../.agents/skills/<name>`, so both read the same files.
|
||||
|
||||
## Library
|
||||
|
||||
The Description column is the first sentence of each installed skill's `SKILL.md` frontmatter `description`. Source is the `source` from `skills.yaml` (GitHub URLs shortened to `owner/repo/tree/<ref>`). Agents `claude-code` means the skill is installed as a real directory in `~/.claude/skills` only.
|
||||
|
||||
| Skill | Source | Agents | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `absurd` | `earendil-works/absurd` | claude-code | Debug and operate Absurd durable workflows with absurdctl. |
|
||||
| `agents-sdk` | `cloudflare/skills` | claude-code | Build, debug, or review Cloudflare Agents SDK applications using the agents package. |
|
||||
| `brandkit` | `Leonxlnx/taste-skill` | universal + claude-code | Premium brand-kit image generation skill for creating high-end brand-guidelines boards, logo systems, identity decks, and visual-world presentations. |
|
||||
| `bun-development` | `sickn33/antigravity-awesome-skills` | universal + claude-code | Fast, modern JavaScript/TypeScript development with the Bun runtime, inspired by [oven-sh/bun](https://github.com/oven-sh/bun). |
|
||||
| `cloudflare` | `cloudflare/skills` | universal + claude-code | Discover and choose Cloudflare products for apps, APIs, AI agents, storage, networking, and security. |
|
||||
| `cloudflare-email-service` | `cloudflare/skills` | claude-code | Implement or troubleshoot Cloudflare Email Sending and Email Routing integrations and their delivery configuration. |
|
||||
| `cloudflare-one` | `cloudflare/skills` | claude-code | Design, configure, troubleshoot, or review Cloudflare One Zero Trust and SASE deployments. |
|
||||
| `cloudflare-one-migrations` | `cloudflare/skills` | claude-code | Assess and plan migrations from existing VPN, SWG, or SASE platforms to Cloudflare One, including policy mapping, parity gaps, and rollout. |
|
||||
| `codebase-design` | `mattpocock/skills` | universal + claude-code | Shared vocabulary for designing deep modules. |
|
||||
| `core` | `vercel-labs/json-render` | universal + claude-code | Core package for defining schemas, catalogs, and AI prompt generation for json-render. |
|
||||
| `create-beads-orchestration` | `AvivK5498/Claude-Code-Beads-Orchestration/tree/main/skills/create-beads-orchestration` | claude-code | Bootstrap lean multi-agent orchestration with beads task tracking. |
|
||||
| `deepinit` | `yeachan-heo/oh-my-claudecode` | universal + claude-code | Deep codebase initialization with hierarchical AGENTS.md documentation |
|
||||
| `design-an-interface` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Generate multiple radically different interface designs for a module using parallel sub-agents. |
|
||||
| `design-taste-frontend` | `Leonxlnx/taste-skill` | universal + claude-code | Anti-slop frontend skill for landing pages, portfolios, and redesigns. |
|
||||
| `diagnosing-bugs` | `mattpocock/skills` | universal + claude-code | Diagnosis loop for hard bugs and performance regressions. |
|
||||
| `domain-modeling` | `mattpocock/skills` | universal + claude-code | Build and sharpen a project's domain model. |
|
||||
| `durable-objects` | `cloudflare/skills` | claude-code | Build, debug, or review Cloudflare Durable Objects code for persistent state and coordination. |
|
||||
| `eli5` | `coldteadotai/pr-lens` | universal + claude-code | Explains a codebase, a folder, a feature, a command or a pull request to someone who knows nothing about it, as a PR Lens canvas whose walkthrough builds the picture one part at a time. |
|
||||
| `find-skills` | `vercel-labs/skills` | universal + claude-code | Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. |
|
||||
| `fumadocs-component-docs` | `TheOrcDev/8bitcn-ui` | universal + claude-code | Create component documentation with installation, usage examples, and preview sections. |
|
||||
| `fumadocs-mdx-structure` | `TheOrcDev/8bitcn-ui` | universal + claude-code | Create MDX documentation files with proper frontmatter, imports, and component integration. |
|
||||
| `fumadocs-registry-integration` | `TheOrcDev/8bitcn-ui` | universal + claude-code | Register 8-bit components in registry.json for shadcn/ui add command. |
|
||||
| `git-guardrails-claude-code` | `mattpocock/skills` | universal + claude-code | Set up Claude Code hooks to block dangerous git commands (push, reset --hard, clean, branch -D, etc.) before they execute. |
|
||||
| `golang-pro` | `jeffallan/claude-skills` | universal + claude-code | Implements concurrent Go patterns using goroutines and channels, designs and builds microservices with gRPC or REST, optimizes Go application performance with pprof, and enforces idiomatic Go with generics, interfaces, and robust error handling. |
|
||||
| `gpt-taste` | `Leonxlnx/taste-skill` | universal + claude-code | Elite UX/UI & Advanced GSAP Motion Engineer. |
|
||||
| `grill-me` | `mattpocock/skills` | universal + claude-code | A relentless interview to sharpen a plan or design. |
|
||||
| `grill-with-docs` | `mattpocock/skills` | universal + claude-code | A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go. |
|
||||
| `grilling` | `mattpocock/skills` | universal + claude-code | Grill the user relentlessly about a plan, decision, or idea. |
|
||||
| `handoff` | `mattpocock/skills` | universal + claude-code | Compact the current conversation into a handoff document for another agent to pick up. |
|
||||
| `high-end-visual-design` | `Leonxlnx/taste-skill` | universal + claude-code | Teaches the AI to design like a high-end agency. |
|
||||
| `imagegen-frontend-web` | `Leonxlnx/taste-skill` | universal + claude-code | Elite frontend image-direction skill for generating premium, conversion-aware website design references. |
|
||||
| `implement` | `mattpocock/skills` | universal + claude-code | Implement a piece of work based on a spec or set of tickets. |
|
||||
| `improve-codebase-architecture` | `mattpocock/skills` | universal + claude-code | Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick. |
|
||||
| `interface-design` | `Dammyjay93/interface-design` | universal + claude-code | Craft-first interface design for dashboards, admin panels, SaaS apps, tools, settings pages, data interfaces, and interactive products. |
|
||||
| `laws-of-ux` | `RoleModel/rolemodel-skills` | universal + claude-code | Review and guide UI implementations using the 21 Laws of UX. |
|
||||
| `linear` | `openai/skills` | universal + claude-code | Manage issues, projects & team workflows in Linear. |
|
||||
| `linear-cli` | `schpet/linear-cli` | universal + claude-code | Manage Linear issues from the command line using the linear cli. |
|
||||
| `linear-documents` | `finesssee/linear-cli` | universal + claude-code | Manage Linear documents. |
|
||||
| `loop-me` | `mattpocock/skills` | universal + claude-code | Grill me about specs for the workflows I want to build, within this workspace. |
|
||||
| `loopy` | `Forward-Future/loop-library` | universal + claude-code | Discover, find, compare, audit, repair, adapt, craft, run, debrief, save, and prepare repeatable AI-agent loops for publication. |
|
||||
| `migrate-to-shoehorn` | `mattpocock/skills` | universal + claude-code | Migrate test files from `as` type assertions to @total-typescript/shoehorn. |
|
||||
| `minimalist-ui` | `Leonxlnx/taste-skill` | universal + claude-code | Clean editorial-style interfaces. |
|
||||
| `mintlify` | `https://mintlify.com/docs` | universal + claude-code | Build and maintain documentation sites with Mintlify. |
|
||||
| `mintlify-api` | `https://mintlify.com/docs` | universal + claude-code | Interact with the Mintlify REST API to manage deployments, trigger builds, and query documentation site metadata programmatically. |
|
||||
| `mintlify-docs` | `https://mintlify.com/docs` | universal + claude-code | Build and maintain documentation sites with Mintlify. |
|
||||
| `mise-tasks` | `terrylica/cc-skills/tree/6d35f84` | universal + claude-code | Orchestrate multi-step project workflows using mise task definitions with dependency management and argument handling. |
|
||||
| `native-sdk` | `vercel-labs/native` | universal + claude-code | Discovery skill for the Native SDK, the complete toolkit for building native desktop applications. |
|
||||
| `no-ai-slop` | `petergyang/no-ai-slop` | universal + claude-code | Edit drafts into sharper, more human writing while preserving the writer's personal voice, or detect AI-slop patterns without rewriting. |
|
||||
| `obsidian-vault` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Search, create, and manage notes in the Obsidian vault with wikilinks and index notes. |
|
||||
| `openlogs-server-logs` | `charlietlamb/openlogs` | universal + claude-code | Fetch and inspect recent local server logs in repos that use openlogs or the `ol` CLI. |
|
||||
| `pr-lens` | `coldteadotai/pr-lens` | universal + claude-code | Draws a code change or part of a codebase as an animated architecture or data-flow diagram, on its own or in a pull request. |
|
||||
| `prototype` | `mattpocock/skills` | universal + claude-code | Build a throwaway prototype to answer a design question. |
|
||||
| `qa` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Interactive QA session where user reports bugs or issues conversationally, and the agent files GitHub issues. |
|
||||
| `react` | `vercel-labs/json-render` | universal + claude-code | React renderer for json-render that turns JSON specs into React components. |
|
||||
| `react-email` | `vercel-labs/json-render` | universal + claude-code | React Email renderer for json-render that turns JSON specs into HTML or plain-text emails using @react-email/components and @react-email/render. |
|
||||
| `redesign-existing-projects` | `Leonxlnx/taste-skill` | universal + claude-code | Upgrades existing websites and apps to premium quality. |
|
||||
| `remix` | `remix-run/remix` | universal + claude-code | Builds, reviews, and refactors Remix 3 apps using the remix package and remix/* imports. |
|
||||
| `remotion-best-practices` | `remotion-dev/skills` | universal + claude-code | Router for all Remotion skills |
|
||||
| `remotion-captions` | `remotion-dev/skills` | universal + claude-code | Transcribing, displaying and animating captions |
|
||||
| `remotion-create` | `remotion-dev/skills` | universal + claude-code | Create a new Remotion video |
|
||||
| `remotion-docs` | `remotion-dev/skills` | universal + claude-code | Search Remotion documentation |
|
||||
| `remotion-interactivity` | `remotion-dev/skills` | universal + claude-code | Structure Remotion markup for interactivity |
|
||||
| `remotion-maps` | `remotion-dev/skills` | universal + claude-code | Remotion Map animation knowledge |
|
||||
| `remotion-markup` | `remotion-dev/skills` | universal + claude-code | Content, animation and effects best practices |
|
||||
| `remotion-multimedia` | `remotion-dev/skills` | universal + claude-code | Interacting with Mediabunny |
|
||||
| `remotion-render` | `remotion-dev/skills` | universal + claude-code | Export a Remotion video |
|
||||
| `remotion-saas` | `remotion-dev/skills` | universal + claude-code | Build an app with Remotion |
|
||||
| `remotion-studio` | `remotion-dev/skills` | universal + claude-code | Preview a Remotion video |
|
||||
| `remotion-upgrade` | `remotion-dev/skills` | universal + claude-code | Upgrade Remotion, and related packages |
|
||||
| `request-refactor-plan` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Create a detailed refactor plan with tiny commits via user interview, then file it as a GitHub issue. |
|
||||
| `resolving-merge-conflicts` | `mattpocock/skills/tree/153fc1b` | universal + claude-code | Use when you need to resolve an in-progress git merge/rebase conflict. |
|
||||
| `rust-async-patterns` | `wshobson/agents` | universal + claude-code | Master Rust async programming with Tokio, async traits, error handling, and concurrent patterns. |
|
||||
| `rust-best-practices` | `apollographql/skills` | universal + claude-code | Guide for writing idiomatic Rust code based on Apollo GraphQL's best practices handbook. |
|
||||
| `rust-skills` | `leonardomso/rust-skills` | universal + claude-code | Comprehensive Rust coding guidelines with 265 rules across 26 categories. |
|
||||
| `rust-testing` | `affaan-m/everything-claude-code` | universal + claude-code | Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. |
|
||||
| `sandbox-sdk` | `cloudflare/skills/tree/30553f8` | claude-code | Build sandboxed applications for secure code execution. |
|
||||
| `scaffold-exercises` | `mattpocock/skills` | universal + claude-code | Create exercise directory structures with sections, problems, solutions, and explainers that pass linting. |
|
||||
| `setup-matt-pocock-skills` | `mattpocock/skills` | universal + claude-code | Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. |
|
||||
| `setup-pre-commit` | `mattpocock/skills` | universal + claude-code | Set up Husky pre-commit hooks with lint-staged (Prettier), type checking, and tests in the current repo. |
|
||||
| `shadcn` | `shadcn-ui/ui` | universal + claude-code | Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. |
|
||||
| `solidity-foundry-development` | `whackur/solidity-agent-toolkit` | universal + claude-code | Foundry development workflow for Solidity smart contracts. |
|
||||
| `solidity-security` | `wshobson/agents` | universal + claude-code | Master smart contract security best practices to prevent common vulnerabilities and implement secure Solidity patterns. |
|
||||
| `stop-slop` | `hardikpandya/stop-slop` | universal + claude-code | Remove AI writing patterns from prose. |
|
||||
| `tanstack-form` | `tanstack-skills/tanstack-skills` | universal + claude-code | Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, Lit, and Svelte. |
|
||||
| `tanstack-integration-best-practices` | `deckardger/tanstack-agent-skills` | universal + claude-code | Best practices for integrating TanStack Query with TanStack Router and TanStack Start. |
|
||||
| `tanstack-query-best-practices` | `deckardger/tanstack-agent-skills` | universal + claude-code | TanStack Query (React Query) best practices for data fetching, caching, mutations, and server state management. |
|
||||
| `tanstack-router-best-practices` | `deckardger/tanstack-agent-skills` | universal + claude-code | TanStack Router best practices for type-safe routing, data loading, search params, and navigation. |
|
||||
| `tanstack-start-best-practices` | `deckardger/tanstack-agent-skills` | universal + claude-code | TanStack Start best practices for full-stack React applications. |
|
||||
| `tanstack-table` | `tanstack-skills/tanstack-skills` | universal + claude-code | Headless UI for building powerful tables & datagrids for TS/JS, React, Vue, Solid, Svelte, Qwik, Angular, and Lit. |
|
||||
| `tdd` | `mattpocock/skills` | universal + claude-code | Test-driven development. |
|
||||
| `teach` | `mattpocock/skills` | universal + claude-code | Teach the user a new skill or concept, within this workspace. |
|
||||
| `todoist-cli` | `Doist/todoist-cli` | universal + claude-code | Manage Todoist tasks, projects, labels, filters, sections, comments, reminders, and workspaces via the `td` CLI. |
|
||||
| `triage` | `mattpocock/skills` | universal + claude-code | Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs. |
|
||||
| `turnstile-spin` | `cloudflare/skills` | claude-code | Set up, repair, or migrate to Cloudflare Turnstile bot verification in an existing frontend and backend, including server-side Siteverify. |
|
||||
| `typescript-expert` | `remix-run/remix` | universal + claude-code | Write, refactor, or review TypeScript code with strict, precise, maintainable types and without unnecessary `any`, type assertions, or type suppressions. |
|
||||
| `ubiquitous-language` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Extract a DDD-style ubiquitous language glossary from the current conversation, flagging ambiguities and proposing canonical terms. |
|
||||
| `ui-ux-pro-max` | `nextlevelbuilder/ui-ux-pro-max-skill` | universal + claude-code | UI/UX design intelligence for web, mobile, and desktop. |
|
||||
| `uv-package-manager` | `wshobson/agents` | universal + claude-code | Master the uv package manager for fast Python dependency management, virtual environments, and modern Python project workflows. |
|
||||
| `web-design-guidelines` | `vercel-labs/agent-skills` | universal + claude-code | Review UI code for Web Interface Guidelines compliance. |
|
||||
| `web-perf` | `cloudflare/skills` | claude-code | Audit, diagnose, or optimize website loading and interaction performance, Core Web Vitals, and Lighthouse performance scores. |
|
||||
| `workers-best-practices` | `cloudflare/skills` | universal + claude-code | Cloudflare Workers best practices for production applications. |
|
||||
| `wrangler` | `cloudflare/skills` | universal + claude-code | Run or troubleshoot Wrangler CLI commands and configure Worker projects for local development, Previews, deployment, and Cloudflare resource management. |
|
||||
| `write-tests` | `remix-run/remix` | universal + claude-code | Write, refactor, or review tests in the Remix repository. |
|
||||
| `writing-beats` | `mattpocock/skills` | universal + claude-code | Writing, exploit; assemble raw material into a journey of beats, grounding each term before a beat leans on it. |
|
||||
| `writing-fragments` | `mattpocock/skills` | universal + claude-code | Writing, explore: mine raw fragments, no structure yet. |
|
||||
| `writing-great-skills` | `mattpocock/skills/tree/v1.1.0` | universal + claude-code | Reference for writing and editing skills well — the vocabulary and principles that make a skill predictable. |
|
||||
| `writing-shape` | `mattpocock/skills` | universal + claude-code | Writing, exploit: shape raw material into an article, paragraph by paragraph. |
|
||||
@@ -0,0 +1,141 @@
|
||||
# How it works
|
||||
|
||||
Source layout, naming conventions, symlink mode, and the day-to-day chezmoi workflow for this repo.
|
||||
|
||||
The repo is a [chezmoi](https://chezmoi.io) source directory. It lives at `~/.local/share/chezmoi` and its remote is [env/cachyos](https://prad.codes/env/cachyos) (`https://prad.codes/env/cachyos.git`, branch `main`). A checkout elsewhere (`~/Code/prad.codes/env/cachyos`) works the same way when chezmoi is pointed at it with `--source`. `chezmoi apply` renders it into `$HOME`. The [Installation](../installation.md) page covers first-time setup.
|
||||
|
||||
## Pages in this section
|
||||
|
||||
| Page | Covers |
|
||||
| --- | --- |
|
||||
| [Config and roles](config.md) | `.chezmoi.toml.tmpl`: prompts, machine role, symlink mode, age encryption, template data |
|
||||
| [Packages and aliases](data.md) | `.chezmoidata/packages.yaml` and `.chezmoidata/aliases.yaml`, and how they render |
|
||||
| [Apply scripts](scripts.md) | Every `run_*` script: when it runs, what triggers it, what it does per OS and role |
|
||||
| [Externals and ignore rules](externals-and-ignore.md) | `.chezmoiexternal.toml` git clones and every `.chezmoiignore.tmpl` rule |
|
||||
|
||||
## Source layout
|
||||
|
||||
```text
|
||||
- .chezmoi.toml.tmpl
|
||||
- .chezmoidata/
|
||||
- aliases.yaml
|
||||
- packages.yaml
|
||||
- skills.yaml
|
||||
- .chezmoiexternal.toml
|
||||
- .chezmoiexternals/
|
||||
- agent-root.toml
|
||||
- ghostty.toml
|
||||
- herdr.toml
|
||||
- kitty.toml
|
||||
- noctalia.toml
|
||||
- omp.toml
|
||||
- television.toml
|
||||
- tmux.toml
|
||||
- tmux-palette.toml
|
||||
- yazi.toml
|
||||
- .chezmoiignore.tmpl
|
||||
- .gitmodules
|
||||
- .chezmoiscripts/
|
||||
- run_onchange_before_10-packages.sh.tmpl
|
||||
- run_onchange_after_15-tools.sh.tmpl
|
||||
- run_onchange_after_20-gh-extensions.sh.tmpl
|
||||
- run_onchange_after_25-skills.sh.tmpl
|
||||
- run_once_after_30-user-services.sh.tmpl
|
||||
- run_onchange_after_40-post.sh.tmpl
|
||||
- run_onchange_after_build-bat-cache.sh.tmpl
|
||||
- run_onchange_after_install-yazi-plugins.sh.tmpl
|
||||
- dot_bashrc
|
||||
- dot_zshrc
|
||||
- dot_gitconfig.tmpl
|
||||
- dot_config/
|
||||
- atuin/
|
||||
- bash/
|
||||
- bat/
|
||||
- drydock/
|
||||
- fish/
|
||||
- fontconfig/
|
||||
- gh/
|
||||
- gh-dash/
|
||||
- go/
|
||||
- herdr-nvim/
|
||||
- hunk/
|
||||
- lazydocker/
|
||||
- lazygit/
|
||||
- mimeapps.list
|
||||
- shell/
|
||||
- starship.toml
|
||||
- systemd/
|
||||
- topgrade.toml
|
||||
- umbriel/
|
||||
- xdg-terminals.list
|
||||
- yazi.toml.tmpl
|
||||
- zsh/
|
||||
- dot_local/
|
||||
- bin/
|
||||
- dot_leetcode/
|
||||
- docs/
|
||||
- README.md
|
||||
- AGENTS.md
|
||||
```
|
||||
|
||||
| Path | Target | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `.chezmoi.toml.tmpl` | `~/.config/chezmoi/chezmoi.toml` | Config template rendered by `chezmoi init`. See [Config and roles](config.md). |
|
||||
| `.chezmoidata/` | — | YAML merged into template data (`.packages`, `.aliases`, `.skills`). See [Packages and aliases](data.md) and [Skills](../ai/skills.md). |
|
||||
| `.chezmoiexternal.toml` | — | Git repos cloned into `$HOME` (nvim config, a bat syntax, tpm). |
|
||||
| `.chezmoiexternals/` | — | One `git-repo` external per app: ghostty, kitty, tmux, tmux-palette, television, yazi, herdr, omp, noctalia, agent-root. Plain config repos cloned into `~/.config/<app>`. See [Externals and ignore rules](externals-and-ignore.md#chezmoiexternals). |
|
||||
| `.gitmodules` | — | `dot_config/fish` is the only git submodule. See [Submodules](externals-and-ignore.md#submodules). |
|
||||
| `.chezmoiignore.tmpl` | — | Target paths chezmoi skips; gates the Linux desktop layer by role. |
|
||||
| `.chezmoiscripts/`, root `run_*` | — | Install and housekeeping scripts. See [Apply scripts](scripts.md). |
|
||||
| `dot_config/` | `~/.config/` | Per-tool config. Documented under [Shell](../shell/README.md), [Terminal](../terminal/README.md), [Tools](../tools/README.md), [Desktop](../desktop/README.md). |
|
||||
| `dot_local/bin/` | `~/.local/bin/` | Standalone scripts shared by every shell. |
|
||||
| `dot_leetcode/` | `~/.leetcode/` | leetcode CLI workspace state. |
|
||||
| `docs/`, `README.md`, `AGENTS.md` | — | These docs, the repo README, and the agent rules. Listed in `.chezmoiignore.tmpl`, never applied. |
|
||||
|
||||
Entries whose source name starts with `.` (`.claude/`, `.crush/`, `.gitignore`) are ignored by chezmoi automatically and stay repo-only. The docs are plain Markdown that Gitea renders; there is no site build.
|
||||
|
||||
## Naming conventions
|
||||
|
||||
chezmoi encodes target names and attributes in source file names. These are the prefixes and suffixes the repo uses:
|
||||
|
||||
| Source form | Meaning | Examples here |
|
||||
| --- | --- | --- |
|
||||
| `dot_` | Target name starts with `.` | `dot_config` → `~/.config`, `dot_zshrc` → `~/.zshrc` |
|
||||
| `private_` | Target has group/world permissions cleared (`0600`/`0700`) | `dot_config/atuin/private_config.toml`, `dot_config/gh/private_config.yml`, `dot_config/hunk/private_state.json` |
|
||||
| `executable_` | Target gets the executable bit | Every script in `dot_local/bin/` |
|
||||
| `exact_` | Directory is exact: entries in the target that are not in the source are deleted | None currently |
|
||||
| `empty_` | File is kept even when empty | None currently |
|
||||
| `symlink_` | Target is a symlink; file content is the link destination | None currently |
|
||||
| `.tmpl` | Rendered with Go templates and chezmoi data | `dot_gitconfig.tmpl`, `dot_config/shell/aliases.sh.tmpl`, all scripts, the config and ignore files |
|
||||
| `run_once_` / `run_onchange_` + `before_` / `after_` | Script, with run frequency and phase | See [Apply scripts](scripts.md) |
|
||||
|
||||
Prefixes stack in chezmoi's fixed order, e.g. `private_dot_foo`, `executable_dot_bar`. No source file currently uses `encrypted_`, even though age encryption is configured.
|
||||
|
||||
## Symlink mode
|
||||
|
||||
`.chezmoi.toml.tmpl` sets `mode = "symlink"`. `chezmoi apply` then makes each managed dotfile a symlink into the source tree instead of a copy, as long as the file is a plain regular file. Files that are templates, `private_`, `executable_`, or encrypted are still written as real files.
|
||||
|
||||
What that means in practice:
|
||||
|
||||
- Editing a plain file in `$HOME` (for example `~/.config/umbriel/binds.toml`) edits `~/.local/share/chezmoi/dot_config/umbriel/binds.toml` directly. The change is live and shows up in `git status` of the repo with no `chezmoi add` step.
|
||||
- Templates are rendered copies. Editing `~/.gitconfig` or `~/.config/shell/aliases.sh` changes only the copy; the next `chezmoi apply` overwrites it. Edit the `.tmpl` source (`chezmoi edit ~/.gitconfig`) instead.
|
||||
- `private_` and `executable_` targets are real files too. After editing one in place, run `chezmoi re-add <path>` to copy it back into the source.
|
||||
- Tools that rewrite their own config write through the symlink into the repo. The runtime state that would otherwise pollute the source (plugin dirs, lock files, `fish_variables`, `__pycache__`) is excluded in `.chezmoiignore.tmpl` and `.gitignore`; see [Externals and ignore rules](externals-and-ignore.md).
|
||||
|
||||
> [!NOTE]
|
||||
> Several fish aliases (`e.f`, `e.k`, `,,`) open the chezmoi source paths directly. Symlink mode makes that equivalent to editing the live config. `e.y` and `e.t` open the yazi and tmux external checkouts in `~/.config`.
|
||||
|
||||
## Daily workflow
|
||||
|
||||
| Command | What it does here |
|
||||
| --- | --- |
|
||||
| `chezmoi apply` | Runs `before_` scripts (package install), writes symlinks/files/templates into `$HOME`, clones missing externals, then runs `after_` scripts. |
|
||||
| `chezmoi update` | `git pull` in the source directory, then `apply`. |
|
||||
| `chezmoi diff` | Shows what `apply` would change, including rendered templates and scripts that would run. |
|
||||
| `chezmoi add <path>` | Moves a new file into the source tree under the right name. In symlink mode the target becomes a symlink on the next apply. Use `--template` or `--encrypt` for templated or secret files. |
|
||||
| `chezmoi re-add <path>` | Copies edits made to non-symlinked targets (private, executable) back into the source. |
|
||||
| `chezmoi edit <path>` | Opens the source file for a target, the right way to change templates. |
|
||||
| `chezmoi cd` | Opens a shell in `~/.local/share/chezmoi` for git work. The fish alias `,,` opens yazi there instead. |
|
||||
| `chezmoi init --prompt` | Re-asks name, email, and role and regenerates `~/.config/chezmoi/chezmoi.toml`. |
|
||||
|
||||
Commit and push from the source directory with plain git; the remote pushes over SSH (`git@prad.codes:env/cachyos.git`).
|
||||
@@ -0,0 +1,96 @@
|
||||
# Config and roles
|
||||
|
||||
The .chezmoi.toml.tmpl config template, its three prompts, symlink mode, age encryption, and where the resulting data is used.
|
||||
|
||||
`.chezmoi.toml.tmpl` is rendered once by `chezmoi init` into `~/.config/chezmoi/chezmoi.toml`. It asks three questions, turns on symlink mode and age encryption, and stores the answers as template data for every other `.tmpl` file.
|
||||
|
||||
## Source
|
||||
|
||||
**.chezmoi.toml.tmpl**
|
||||
|
||||
```toml
|
||||
{{- $name := promptStringOnce . "name" "Full name" "Prad Nukala" -}}
|
||||
{{- $email := promptStringOnce . "email" "Email" "prad@sonr.io" -}}
|
||||
{{- $roleDefault := "desktop" -}}
|
||||
{{- $role := promptChoiceOnce . "role" "Machine role" (list "desktop" "laptop" "server") $roleDefault -}}
|
||||
|
||||
mode = "symlink"
|
||||
encryption = "age"
|
||||
|
||||
[age]
|
||||
identity = "~/.config/chezmoi/key.txt"
|
||||
recipient = "age19tdqgavaekhvgqsd75n8a09vvshen2ykzy8kw6tenczz73449anqc3l7a6"
|
||||
useBuiltin = true
|
||||
|
||||
[data]
|
||||
name = {{ $name | quote }}
|
||||
email = {{ $email | quote }}
|
||||
role = {{ $role | quote }}
|
||||
```
|
||||
|
||||
## Prompts
|
||||
|
||||
| Key | Prompt | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| `name` | `Full name` | `promptStringOnce` | `Prad Nukala` |
|
||||
| `email` | `Email` | `promptStringOnce` | `prad@sonr.io` |
|
||||
| `role` | `Machine role` | `promptChoiceOnce` from `desktop`, `laptop`, `server` | `desktop` |
|
||||
|
||||
The `*Once` functions only prompt when the key is missing from the existing config, so re-running `chezmoi init` keeps earlier answers. `chezmoi init --prompt` forces all three prompts again. Changing the role changes the rendered ignore file and package script, so the next `chezmoi apply` adds or drops the desktop layer and re-runs the package install.
|
||||
|
||||
### Machine roles
|
||||
|
||||
| Role | Intended platform | Linux desktop layer | Desktop packages (`packages.arch.desktop`) |
|
||||
| --- | --- | --- | --- |
|
||||
| `desktop` | CachyOS / Arch Linux | Applied | Installed |
|
||||
| `laptop` | CachyOS / Arch Linux | Applied | Installed |
|
||||
| `server` | Arch Linux, headless | Skipped | Skipped |
|
||||
|
||||
`desktop` and `laptop` behave identically in the current templates; nothing branches on one without the other. The desktop layer is also skipped whenever the OS is not Linux, whatever the role. See [the ignore rules](externals-and-ignore.md#chezmoiignoretmpl) for the exact list of gated paths.
|
||||
|
||||
## Mode
|
||||
|
||||
`mode = "symlink"` makes `chezmoi apply` link plain files in `$HOME` back to the source tree. Templates, `private_`, `executable_`, and encrypted files are still written as real files. The [overview](README.md#symlink-mode) covers the editing consequences.
|
||||
|
||||
## Age encryption
|
||||
|
||||
| Setting | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `encryption` | `"age"` | `chezmoi add --encrypt` and `encrypted_` source files use age. |
|
||||
| `age.identity` | `~/.config/chezmoi/key.txt` | Private key used to decrypt. Distributed by hand between machines, never committed. |
|
||||
| `age.recipient` | `age19tdqgavaekhvgqsd75n8a09vvshen2ykzy8kw6tenczz73449anqc3l7a6` | Public key files are encrypted to. Safe to publish. |
|
||||
| `age.useBuiltin` | `true` | Uses chezmoi's built-in age implementation; the `age` binary is not required. |
|
||||
|
||||
No source file currently carries the `encrypted_` attribute, so the key is only exercised when a secret is added with `chezmoi add --encrypt`. The [installation page](../installation.md#2-age-key) asks for the key to be copied into place before the first apply:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/chezmoi
|
||||
scp <existing-machine>:.config/chezmoi/key.txt ~/.config/chezmoi/key.txt
|
||||
chmod 600 ~/.config/chezmoi/key.txt
|
||||
```
|
||||
|
||||
## Template data
|
||||
|
||||
The `[data]` table exposes `.name`, `.email`, and `.role` to every template. chezmoi also merges `.chezmoidata/*.yaml` into the same namespace (`.packages`, `.aliases`; see [Packages and aliases](data.md)) and provides built-ins such as `.chezmoi.os`.
|
||||
|
||||
| Variable | Consumed by | Use |
|
||||
| --- | --- | --- |
|
||||
| `.name` | `dot_gitconfig.tmpl` | `[user] name` in `~/.gitconfig` |
|
||||
| `.email` | `dot_gitconfig.tmpl` | `[user] email` in `~/.gitconfig` |
|
||||
| `.role` | `.chezmoiignore.tmpl` | Skips the Linux desktop layer unless role is `desktop` or `laptop` |
|
||||
| `.role` | `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` | Adds the `arch.desktop` pacman and AUR packages for `desktop`/`laptop` |
|
||||
| `.chezmoi.os` | `.chezmoiignore.tmpl` | Desktop layer requires `linux` |
|
||||
| `.chezmoi.os` | `run_onchange_before_10-packages.sh.tmpl` | pacman/yay on `linux` |
|
||||
| `.chezmoi.os` | `run_once_after_30-user-services.sh.tmpl` | systemd user units on `linux` only |
|
||||
| `.chezmoi.os` | `run_onchange_after_40-post.sh.tmpl` | `fc-cache` on `linux` only |
|
||||
| `.chezmoi.os`, `.role` | `run_onchange_after_15-tools.sh.tmpl` | strata install on `linux` with role `desktop`/`laptop` |
|
||||
|
||||
**dot_gitconfig.tmpl**
|
||||
|
||||
```ini
|
||||
[user]
|
||||
email = {{ .email }}
|
||||
name = {{ .name }}
|
||||
```
|
||||
|
||||
The rest of `~/.gitconfig` is covered under [Tools](../tools/README.md).
|
||||
@@ -0,0 +1,183 @@
|
||||
# Packages and aliases
|
||||
|
||||
The .chezmoidata YAML files that declare every installed package and every shared shell alias, and how they render.
|
||||
|
||||
Files in `.chezmoidata/` are merged into chezmoi's template data on every run. Nothing in the directory is written to `$HOME`; the data only reaches the machine through templates and scripts.
|
||||
|
||||
| File | Data key | Consumed by |
|
||||
| --- | --- | --- |
|
||||
| `.chezmoidata/packages.yaml` | `.packages` | [`run_onchange_before_10-packages.sh.tmpl`](scripts.md#10-packages), [`run_onchange_after_15-tools.sh.tmpl`](scripts.md#15-tools), [`run_onchange_after_20-gh-extensions.sh.tmpl`](scripts.md#20-gh-extensions) |
|
||||
| `.chezmoidata/aliases.yaml` | `.aliases` | `dot_config/shell/aliases.sh.tmpl` (bash and zsh) |
|
||||
| `.chezmoidata/skills.yaml` | `.skills` | [`run_onchange_after_25-skills.sh.tmpl`](scripts.md#25-skills) |
|
||||
|
||||
## packages.yaml
|
||||
|
||||
One Arch manifest plus a `shared` block. Any edit to the file changes the hash embedded in the package script, so the next `chezmoi apply` re-runs the install. Installs are idempotent (`pacman --needed`, `yay`/`paru --needed`). The lists are derived from what the configs actually invoke: tmux-palette items, television cable `requirements`, fish functions, `~/.local/bin` scripts, and Umbriel bindings.
|
||||
|
||||
### Arch
|
||||
|
||||
`packages.arch.pacman`, installed on every machine with `pacman -S --needed --noconfirm`:
|
||||
|
||||
| Group | Packages |
|
||||
| --- | --- |
|
||||
| Shell and editors | `fish`, `neovim`, `helix`, `micro`, `tmux` |
|
||||
| Terminals | `ghostty` (default), `kitty` |
|
||||
| TUI tools (tmux-palette, television cables) | `yazi`, `lazygit`, `lazydocker`, `docker`, `docker-compose`, `btop`, `fastfetch`, `onefetch`, `television`, `serie`, `tig`, `gitui`, `tldr`, `navi`, `mdfried` |
|
||||
| VCS and forge | `git`, `github-cli`, `git-delta`, `diffnav`, `tea` |
|
||||
| Search and files | `fzf`, `fd`, `ripgrep`, `zoxide`, `eza`, `bat`, `jq`, `duf` |
|
||||
| Media and preview (yazi and tv deps) | `ffmpeg`, `ffmpegthumbnailer`, `poppler`, `resvg`, `imagemagick`, `chafa`, `7zip`, `unzip` |
|
||||
| Remote | `mosh`, `sshfs`, `tailscale` |
|
||||
| Misc CLI | `mise`, `gum`, `glow`, `atuin`, `wget`, `starship`, `zsh-syntax-highlighting` |
|
||||
| Fonts | `ttf-victor-mono-nerd` |
|
||||
|
||||
`packages.arch.aur`, installed with `yay -S --needed --noconfirm` (or `paru`) on every machine: `topgrade`, `portop`. Names some installed package already provides are skipped, and yay is bootstrapped when no helper exists ([10-packages](scripts.md#10-packages)).
|
||||
|
||||
`packages.arch.desktop`, added only for roles `desktop` and `laptop`:
|
||||
|
||||
| Source | Packages |
|
||||
| --- | --- |
|
||||
| pacman | `noctalia`, `polkit`, `xdg-user-dirs`, `xdg-terminal-exec`; Wayland utilities `wl-clipboard`, `cliphist`, `wtype`, `ydotool`, `grim`, `slurp`, `pamixer`, `brightnessctl`, `libnotify`; apps `nautilus`, `imv`, `mpv`, `evince`, `chromium`, `obsidian`; strata runtime deps `bubblewrap`, `desktop-file-utils`, `fontconfig`, `gstreamer`, `gst-libav`, `gst-plugins-base`, `gst-plugins-good`, `gtk4`, `gtksourceview5`, `gvfs`, `poppler-glib`, `xdg-utils` |
|
||||
| AUR (or a sync repo with the same name) | `tldraw-offline-bin`, `bibata-cursor-translucent` (the `Bibata_Ghost` cursor), `umbriel-git` (pulls in `xdg-desktop-portal-umbriel-git`; the `[cachyos]` `umbriel` lacks `window-move-by`), `herdr-bin`, `voxtype-bin`; desktop apps `beeper-bin`, `fastmail`, `1password`, `brave-origin-bin`, `claude-desktop`, `spotify` |
|
||||
|
||||
strata (the folder handler) has no package; [15-tools](scripts.md#15-tools) runs its installer.
|
||||
|
||||
### Shared
|
||||
|
||||
`packages.shared.gh_extensions`, installed by the gh-extensions script on every machine:
|
||||
|
||||
| Extension | Purpose |
|
||||
| --- | --- |
|
||||
| `davidraviv/gh-clean-branches` | `gh clean-branches`: delete local branches whose upstream is gone |
|
||||
| `prdlk/gh-commit` | `gh commit` |
|
||||
| `dlvhdr/gh-dash` | `gh dash`: PR and issue dashboard |
|
||||
| `meiji163/gh-notify` | `gh notify`: notifications in the terminal |
|
||||
|
||||
`packages.shared.cargo` lists `suvadu`, `mandible` (the tmux-palette "mandible" item runs `~/.local/bin/mandible-pick`), and `worktrunk` (`wt`, git worktree management for parallel agent work). [`run_onchange_after_15-tools.sh.tmpl`](scripts.md#15-tools) installs rustup when `cargo` is missing, then any crate `cargo install --list` lacks.
|
||||
|
||||
`packages.shared.mise` lists the global mise tools: `ghq`, `bun`, `node`, `uv`, `opencode`, `crush`, and omp as `github:can1357/oh-my-pi` (omp is not in the mise registry, so it uses the GitHub-releases backend, which installs the release binary as `omp`). The same script runs `mise use -g` for each one not yet in the global config (`~/.config/mise/config.toml`, local per machine), then `mise install` on the list. A tool already there keeps its pinned version. topgrade's mise step (`mise upgrade`) updates them.
|
||||
|
||||
> [!NOTE]
|
||||
> AUR packages need `yay` or `paru`. The package script bootstraps `yay` when neither exists; only when that is impossible (running as root with no repo shipping yay) does it print a warning listing the skipped AUR packages and continue.
|
||||
|
||||
## aliases.yaml
|
||||
|
||||
The single source for aliases shared across shells. Each entry has a `section` and a list of `{ name, cmd }` items.
|
||||
|
||||
### Editors / file managers
|
||||
|
||||
| Alias | Command |
|
||||
| --- | --- |
|
||||
| `e` | `nvim` |
|
||||
| `e.d` | `cd ~/.config/dotstate/storage/Rogz/.config && yazi` |
|
||||
| `e.f` | `cd ~/.config/fish && yazi` |
|
||||
| `e.k` | `yazi ~/.config/kitty` |
|
||||
| `e.v` | `yazi ~/.config/nvim/lua` |
|
||||
| `e.y` | `yazi ~/.config/yazi` |
|
||||
| `e.t` | `nvim ~/.config/tmux/tmux.conf` |
|
||||
| `hx` | `helix` |
|
||||
|
||||
### Quick directory jumps
|
||||
|
||||
| Alias | Command |
|
||||
| --- | --- |
|
||||
| `cdc` | `cd ~/.config && yazi` |
|
||||
| `cdd` | `cd ~/Developer && yazi` |
|
||||
| `cdk` | `cd ~/Documents/Knowledge && obsidian` |
|
||||
| `cdl` | `cd ~/.local && yazi` |
|
||||
| `cds` | `cd ~/.local/share/services && ls` |
|
||||
| `cdw` | `cd ~/Downloads && yazi` |
|
||||
|
||||
### Tools
|
||||
|
||||
| Alias | Command |
|
||||
| --- | --- |
|
||||
| `dd` | `lazydocker` |
|
||||
| `gg` | `lazygit` |
|
||||
| `ff` | `exec fish` |
|
||||
| `npx` | `bunx` |
|
||||
| `sm` | `sudo micro` |
|
||||
| `sd` | `ssh prad@<lan-host>` |
|
||||
| `oc` | `opencode` |
|
||||
| `dss` | `dotstate sync` |
|
||||
| `fabric` | `fabric-ai` |
|
||||
| `bk` | `bead-kanban` |
|
||||
| `sua` | `shelly upgrade-all -n` |
|
||||
| `lc` | `leetcode` |
|
||||
| `tk` | `task` |
|
||||
|
||||
`dd` shadows coreutils `dd` in interactive shells.
|
||||
|
||||
## How aliases render
|
||||
|
||||
### bash and zsh
|
||||
|
||||
`dot_config/shell/aliases.sh.tmpl` renders to `~/.config/shell/aliases.sh`. Because it is a template it is written as a real file, not a symlink; edit the YAML and run `chezmoi apply` to regenerate it.
|
||||
|
||||
**dot_config/shell/aliases.sh.tmpl**
|
||||
|
||||
```sh
|
||||
{{- range .aliases }}
|
||||
|
||||
# {{ .section }}
|
||||
{{- range .items }}
|
||||
alias {{ .name }}='{{ .cmd }}'
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
```
|
||||
|
||||
After the generated block the file adds shell-specific pieces that are not in the YAML:
|
||||
|
||||
| Name | Definition |
|
||||
| --- | --- |
|
||||
| `cpy` / `pst` | `wl-copy` / `wl-paste` when `wl-copy` exists |
|
||||
| `q` | Function: if `$YAZI_ID` is set and `ya` exists, runs `ya emit quit` so the parent yazi also exits, then `exit` |
|
||||
| `celar`, `claer` | Typo guards for `clear` |
|
||||
| `ls` | `eza --icons`, when `eza` is installed |
|
||||
|
||||
`~/.bashrc` and `~/.zshrc` source it for interactive shells:
|
||||
|
||||
**dot_bashrc**
|
||||
|
||||
```sh
|
||||
[ -f "$HOME/.config/shell/aliases.sh" ] && . "$HOME/.config/shell/aliases.sh"
|
||||
```
|
||||
|
||||
See [bash and zsh](../shell/bash-zsh.md) for the rest of those files.
|
||||
|
||||
### fish
|
||||
|
||||
`dot_config/fish/conf.d/20-aliases.fish` is not a template. It is a hand-maintained plain file (symlinked into `~/.config/fish/conf.d/`) whose header says it is generated from the YAML; the YAML header likewise names a `20-aliases.fish.tmpl` that does not exist. The fish file has drifted from `aliases.yaml`:
|
||||
|
||||
| Alias | aliases.yaml (bash/zsh) | fish |
|
||||
| --- | --- | --- |
|
||||
| `e.d` | `cd ~/.config/dotstate/storage/Rogz/.config && yazi` | not defined |
|
||||
| `e.f` | `cd ~/.config/fish && yazi` | `cd ~/.local/share/chezmoi/dot_config/fish && yazi` |
|
||||
| `e.h` | not defined | `cd ~/.local/share/chezmoi/dot_config/hypr && yazi` (this repo has no `hypr` directory) |
|
||||
| `e.k` | `yazi ~/.config/kitty` | ✓ |
|
||||
| `e.n` | not defined | `yazi ~/.local/share/chezmoi/dot_config/niri` (this repo has no `niri` directory) |
|
||||
| `e.y` | `yazi ~/.config/yazi` | ✓ |
|
||||
| `e.t` | `nvim ~/.config/tmux/tmux.conf` | ✓ |
|
||||
| `cdd` | `cd ~/Developer && yazi` | not defined |
|
||||
| `,,` | not defined | `cd ~/.local/share/chezmoi && yazi` |
|
||||
| `..` | not defined | `cd ..` |
|
||||
|
||||
fish-only Bun aliases:
|
||||
|
||||
| Alias | Command |
|
||||
| --- | --- |
|
||||
| `br` | `bun run` |
|
||||
| `br.d` | `bun run dev` |
|
||||
| `br.b` | `bun run build` |
|
||||
| `br.t` | `bun run test` |
|
||||
| `bi` | `bun install` |
|
||||
| `bg` | `bun install -g` (shadows fish's `bg` job-control builtin) |
|
||||
| `bx` | `bunx` |
|
||||
|
||||
Every other YAML alias (`e`, `e.v`, `hx`, `cdc`, `cdk`, `cdl`, `cds`, `cdw`, and the whole Tools section) is defined identically in fish. The file ends with the same extras as the sh version: `cpy`/`pst` clipboard detection, the `q` function, and, in interactive sessions only, `celar`/`claer` and `ls` → `eza --icons`. `clear` itself is an autoloaded function (`functions/clear.fish`), which is why it is not aliased. See [fish](../shell/fish.md).
|
||||
|
||||
> [!TIP]
|
||||
> To add an alias for every shell, add it to `aliases.yaml` for bash/zsh and to `20-aliases.fish` by hand for fish.
|
||||
|
||||
## skills.yaml
|
||||
|
||||
Declares the agent skills installed by the Skills CLI. `skills.cli` is the pinned CLI (`skills@1.7.0`, run with `npx -y`), `skills.agents` the default install targets (`[universal, claude-code]`), and `skills.sources` a list of `{ source, skills, agents? }` entries: 108 skills across 42 entries. The full list, pinned sources, and add/remove workflow are on the [Skills](../ai/skills.md) page.
|
||||
@@ -0,0 +1,183 @@
|
||||
# Externals and ignore rules
|
||||
|
||||
The git repos chezmoi clones into $HOME and every rule in .chezmoiignore.tmpl, including the role gate for the desktop layer.
|
||||
|
||||
## .chezmoiexternal.toml
|
||||
|
||||
Externals are content chezmoi fetches instead of storing in the repo. Every entry is a `git-repo` external: chezmoi runs `git clone` when the target directory is missing. Because they are real git checkouts, they are never symlinked into the source tree.
|
||||
|
||||
**.chezmoiexternal.toml**
|
||||
|
||||
```toml
|
||||
[".config/nvim"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/prdlk/nvim.git"
|
||||
[".config/nvim".pull]
|
||||
args = ["--ff-only"]
|
||||
|
||||
[".config/bat/syntaxes/sublime-purescript-syntax"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/tellnobody1/sublime-purescript-syntax.git"
|
||||
[".config/bat/syntaxes/sublime-purescript-syntax".pull]
|
||||
args = ["--ff-only"]
|
||||
|
||||
[".config/tmux/plugins/tpm"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/tmux-plugins/tpm.git"
|
||||
[".config/tmux/plugins/tpm".pull]
|
||||
args = ["--ff-only"]
|
||||
```
|
||||
|
||||
| Target | Repo | Used by |
|
||||
| --- | --- | --- |
|
||||
| `~/.config/nvim` | [prdlk/nvim](https://github.com/prdlk/nvim) | Neovim config, kept in its own repo |
|
||||
| `~/.config/bat/syntaxes/sublime-purescript-syntax` | [tellnobody1/sublime-purescript-syntax](https://github.com/tellnobody1/sublime-purescript-syntax) | PureScript syntax for bat, compiled into bat's cache by the [bat cache scripts](scripts.md#build-bat-cache) |
|
||||
| `~/.config/tmux/plugins/tpm` | [tmux-plugins/tpm](https://github.com/tmux-plugins/tpm) | tmux plugin manager, run from the bottom of `tmux.conf` ([tmux](../terminal/tmux.md)). Cloned inside the tmux checkout, whose `.gitignore` excludes `plugins/` |
|
||||
|
||||
### No refreshPeriod
|
||||
|
||||
No entry in `.chezmoiexternal.toml` sets `refreshPeriod`, so it stays at chezmoi's default of never. chezmoi clones each repo on a fresh machine and otherwise leaves it alone: `chezmoi apply` and `chezmoi update` do not pull. The local nvim checkout always wins over the remote, so in-progress Neovim edits and unpushed commits are never disturbed by an apply.
|
||||
|
||||
To update explicitly, run `chezmoi apply --refresh-externals` (`-R`), which pulls with `git pull --ff-only`. `--ff-only` makes the pull fail instead of creating a merge commit when the local checkout has diverged.
|
||||
|
||||
## .chezmoiexternals/
|
||||
|
||||
Per-app config repos, one file each in `.chezmoiexternals/<app>.toml`. Each is a `git-repo` external with `refreshPeriod = "168h"` and `pull.args = ["--ff-only"]`, so `chezmoi apply` fast-forwards them at most once a week. The repos hold plain config files (no chezmoi prefixes) and are not part of `chezmoi managed` contents. The exception is [`agent-root.toml`](#agent-root), a single downloaded script.
|
||||
|
||||
**.chezmoiexternals/tmux.toml**
|
||||
|
||||
```toml
|
||||
[".config/tmux"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/prdlk/tmux.git"
|
||||
refreshPeriod = "168h"
|
||||
[".config/tmux".pull]
|
||||
args = ["--ff-only"]
|
||||
```
|
||||
|
||||
| File | Target | Repo |
|
||||
| --- | --- | --- |
|
||||
| `tmux.toml` | `~/.config/tmux` | [prdlk/tmux](https://github.com/prdlk/tmux) |
|
||||
| `tmux-palette.toml` | `~/.config/tmux-palette` | [prdlk/tmux-palette](https://github.com/prdlk/tmux-palette) |
|
||||
| `television.toml` | `~/.config/television` | [prdlk/television](https://github.com/prdlk/television) |
|
||||
| `yazi.toml` | `~/.config/yazi` | [prdlk/yazi](https://github.com/prdlk/yazi) |
|
||||
| `kitty.toml` | `~/.config/kitty` | [prdlk/kitty](https://github.com/prdlk/kitty) |
|
||||
| `ghostty.toml` | `~/.config/ghostty` | [prdlk/ghostty](https://github.com/prdlk/ghostty) |
|
||||
| `herdr.toml` | `~/.config/herdr` | [prdlk/herdr](https://github.com/prdlk/herdr) — `plugins/github/` (runtime plugin checkouts) is gitignored there |
|
||||
| `omp.toml` | `~/.omp` | [prdlk/omp](https://github.com/prdlk/omp) |
|
||||
| `noctalia.toml` | `~/.config/noctalia` | [prdlk/noctalia](https://github.com/prdlk/noctalia) — hand-written `config.toml` (shell, screenshot folder, theme, plugins, night light, notifications, OSD, wallpaper), `bar.toml` (bar layout, per-monitor overrides, widgets), and `palettes/` (custom palette JSON); merged under the GUI's unmanaged `~/.local/state/noctalia/settings.toml`, which wins on conflicts; `plugins/` holds runtime state with embedded tokens and is gitignored there. desktop/laptop roles only ([role gate](#chezmoiignoretmpl)) |
|
||||
|
||||
Edit these in place under `~/.config/<app>`, then commit and push from that checkout.
|
||||
|
||||
### agent-root
|
||||
|
||||
`agent-root.toml` is the one `file` external: chezmoi downloads [jankeesvw/agent-root](https://github.com/jankeesvw/agent-root)'s single bash script to `~/.local/bin/agent-root` (mode `0755`) and re-fetches it at most once a week (`refreshPeriod = "168h"`). Coding agents run `agent-root "<why>" <command>` instead of `sudo`; it wraps `pkexec` (`polkit`) so the session's polkit agent (Noctalia's built-in agent) shows the command, and `notify-send` (`libnotify`) shows the reason. Exit 126 means the dialog was dismissed. The usage rules agents follow live in the global omp instructions, `~/.omp/agent/AGENTS.md` ([prdlk/omp](https://github.com/prdlk/omp)). desktop/laptop roles only ([role gate](#chezmoiignoretmpl)), since there is no polkit agent elsewhere.
|
||||
|
||||
**.chezmoiexternals/agent-root.toml**
|
||||
|
||||
```toml
|
||||
[".local/bin/agent-root"]
|
||||
type = "file"
|
||||
url = "https://raw.githubusercontent.com/jankeesvw/agent-root/main/agent-root"
|
||||
executable = true
|
||||
refreshPeriod = "168h"
|
||||
```
|
||||
|
||||
## Submodules
|
||||
|
||||
One source directory is a git submodule, listed in `.gitmodules`. It keeps chezmoi source naming and is applied in symlink mode like the rest of the tree. `chezmoi init` clones it.
|
||||
|
||||
| Source | Target | Repo |
|
||||
| --- | --- | --- |
|
||||
| `dot_config/fish` | `~/.config/fish` | [prdlk/fish](https://github.com/prdlk/fish) |
|
||||
|
||||
Edit it inside the submodule in the chezmoi source, commit there, then commit the updated submodule pointer in the root repo.
|
||||
|
||||
## .chezmoiignore.tmpl
|
||||
|
||||
Patterns match target paths (relative to `$HOME`). Matching entries are neither written nor removed. The file is a template, so the desktop block appears only on machines where it applies. Source entries that start with `.` (`.claude/`, `.crush/`, `.gitignore`) are ignored by chezmoi without needing a rule.
|
||||
|
||||
### Repo docs
|
||||
|
||||
```txt
|
||||
README.md
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
docs
|
||||
```
|
||||
|
||||
Files at the source root that would otherwise land in `$HOME` as `~/README.md` or `~/docs`. This covers the repo README, the agent instruction files, and these docs.
|
||||
|
||||
### Runtime and plugin state inside managed trees
|
||||
|
||||
| Pattern | Why |
|
||||
| --- | --- |
|
||||
| `.config/lazygit/.omc/**` | Tool state written inside the lazygit config dir |
|
||||
| `.config/tmux/plugins/**`, except `!.config/tmux/plugins/tpm` | Plugins tpm installs with `prefix I`; tpm itself is the external above |
|
||||
| `.config/fish/fish_variables` | fish universal variables, rewritten by fish at runtime |
|
||||
| `.config/fish/conf.d/secrets.fish` | Plaintext credentials. Local-only, never in git (the fish repo's `.gitignore` also lists it) |
|
||||
| `.config/nvim/lazy-lock.json` | lazy.nvim lockfile inside the nvim external |
|
||||
| `.config/nvim/.git`, `.config/nvim/.git/**` | The external's git metadata |
|
||||
| `.config/nvim/.omc/**`, `.config/nvim/.crush/**`, `.config/nvim/.understand-anything/**` | Agent tool state inside the nvim checkout |
|
||||
|
||||
`.gitignore` covers the source side of the same problem: `dot_config/lazygit/dot_omc` and `__pycache__/` never get committed even though symlink mode lets tools write into the source tree.
|
||||
|
||||
### mise
|
||||
|
||||
```txt
|
||||
.config/mise
|
||||
.config/mise/**
|
||||
```
|
||||
|
||||
mise tool versions stay local to each machine and are not managed.
|
||||
|
||||
### Shipped by the fish submodule, never applied
|
||||
|
||||
```txt
|
||||
.config/fish/auto-Niri.fish
|
||||
```
|
||||
|
||||
`dot_config/fish` is the shared fish repo, which also carries an auto-start hook for the niri compositor. This repo does not run niri, so the hook is ignored on every role and OS.
|
||||
|
||||
### Desktop layer (role-gated)
|
||||
|
||||
**.chezmoiignore.tmpl**
|
||||
|
||||
```txt
|
||||
{{ if or (ne .chezmoi.os "linux") (and (ne .role "desktop") (ne .role "laptop")) }}
|
||||
.config/umbriel
|
||||
.config/umbriel/**
|
||||
.config/noctalia
|
||||
.config/noctalia/**
|
||||
.local/bin/agent-root
|
||||
.config/fontconfig
|
||||
.config/fontconfig/**
|
||||
.config/mimeapps.list
|
||||
.config/xdg-terminals.list
|
||||
.config/systemd
|
||||
.config/systemd/**
|
||||
.local/bin/kitty-day-save
|
||||
.local/bin/umbriel-launch
|
||||
.local/bin/umbriel-smart-move
|
||||
.local/bin/umbriel-float-square
|
||||
.local/bin/cp-appid
|
||||
.local/bin/projects-sync
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
The block is emitted, and these paths skipped, unless the OS is `linux` and the role is `desktop` or `laptop`. `server` machines never receive the Umbriel config (`dot_config/umbriel/`), the Noctalia external (`~/.config/noctalia`, never cloned there), fontconfig (`dot_config/fontconfig/fonts.conf`), default-app associations (`dot_config/mimeapps.list`), the terminal list (`dot_config/xdg-terminals.list`), the systemd user units (`dot_config/systemd/user/`), the `agent-root` script, or the Umbriel, kitty-day-save, cp-appid, and projects-sync scripts.
|
||||
|
||||
| Machine | Desktop layer |
|
||||
| --- | --- |
|
||||
| `desktop` or `laptop` | Applied |
|
||||
| `server` | Ignored |
|
||||
|
||||
See [Desktop](../desktop/README.md) for what the layer contains.
|
||||
|
||||
### Generated theme files
|
||||
|
||||
```txt
|
||||
.config/umbriel/noctalia.toml
|
||||
```
|
||||
|
||||
Noctalia's umbriel template rewrites this file on every theme change, so it stays out of the repo on every machine. `config.toml` includes it as an optional file; see [Umbriel](../desktop/umbriel.md#theme).
|
||||
@@ -0,0 +1,117 @@
|
||||
# Apply scripts
|
||||
|
||||
Every run_ script chezmoi executes during apply, when each one fires, and what it does per role.
|
||||
|
||||
Seven scripts run during `chezmoi apply`. Five live in `.chezmoiscripts/`, a directory whose scripts run as if they sat at the source root without creating a matching target directory. Two sit at the repo root. All seven are templates; `20-gh-extensions` is a fish script, the rest use `#!/bin/sh` with `set -eu` (except `25-skills`, which uses `set -u`).
|
||||
|
||||
## Execution order
|
||||
|
||||
`before_` scripts run before any file is written; `after_` scripts run after every file, symlink, and external is in place. Within a phase chezmoi runs scripts in ASCII order of their target names, so the numeric prefixes come first.
|
||||
|
||||
1. **10-packages (before, onchange)**: Install system packages from `packages.yaml`.
|
||||
2. **Files, symlinks, externals**: chezmoi writes the target state.
|
||||
3. **15-tools (after, onchange)**: rustup and cargo crates, omp, global mise tools, strata on desktops.
|
||||
4. **20-gh-extensions (after, onchange)**: Install gh extensions from a fish session.
|
||||
5. **25-skills (after, onchange)**: Install and prune agent skills.
|
||||
6. **30-user-services (after, once)**: Enable systemd user units (kitty-day-save, projects-sync.timer).
|
||||
7. **40-post (after, onchange)**: bat cache, font cache.
|
||||
8. **build-bat-cache (after, onchange)**: Rebuild bat cache when its theme or config changes.
|
||||
9. **install-yazi-plugins (after, onchange)**: Reinstall yazi plugins when the manifest changes.
|
||||
|
||||
| Script | Phase | Frequency | Re-run trigger |
|
||||
| --- | --- | --- | --- |
|
||||
| `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` | before | onchange | sha256 of `.chezmoidata/packages.yaml`; also the role, which changes the rendered package list |
|
||||
| `.chezmoiscripts/run_onchange_after_15-tools.sh.tmpl` | after | onchange | sha256 of `packages.shared.cargo` and `packages.shared.mise` as JSON; also the role, which adds the strata step |
|
||||
| `.chezmoiscripts/run_onchange_after_20-gh-extensions.sh.tmpl` | after | onchange | sha256 of `packages.shared.gh_extensions` as JSON |
|
||||
| `.chezmoiscripts/run_onchange_after_25-skills.sh.tmpl` | after | onchange | sha256 of `.skills` as JSON |
|
||||
| `.chezmoiscripts/run_once_after_30-user-services.sh.tmpl` | after | once | Runs once per distinct rendered content |
|
||||
| `.chezmoiscripts/run_onchange_after_40-post.sh.tmpl` | after | onchange | Edits to the script itself |
|
||||
| `run_onchange_after_build-bat-cache.sh.tmpl` | after | onchange | sha256 of `dot_config/bat/themes/cyberdream.tmTheme` and `dot_config/bat/config` |
|
||||
| `run_onchange_after_install-yazi-plugins.sh.tmpl` | after | onchange | sha256 of `~/.config/yazi/package.toml` (empty when the file is absent) |
|
||||
|
||||
`run_onchange_` scripts re-run whenever their rendered content differs from the last successful run. The hash triggers work by writing a checksum of another file into a comment, so editing that file changes the script's content:
|
||||
|
||||
```sh
|
||||
# packages.yaml hash: {{ include ".chezmoidata/packages.yaml" | sha256sum }}
|
||||
```
|
||||
|
||||
`run_once_` scripts run once per unique rendered content and again only if that content changes or the previous run failed.
|
||||
|
||||
## 10-packages
|
||||
|
||||
`.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` installs everything in [`packages.yaml`](data.md#packagesyaml). It runs before files are applied so the tools exist when configs land.
|
||||
|
||||
1. Uses `pacman` directly when running as root, otherwise `sudo pacman`.
|
||||
2. `pacman -T` filters each list down to names no installed package provides (so `mise-bin` satisfies `mise`, `yazi-git` satisfies `yazi`), then `pacman -S --needed --noconfirm` installs the rest of `packages.arch.pacman`.
|
||||
3. For roles `desktop` and `laptop`, the same for `packages.arch.desktop.pacman`.
|
||||
4. Builds the AUR list: `packages.arch.desktop.aur` plus `packages.arch.aur` for `desktop`/`laptop`, only `packages.arch.aur` for other roles, then drops every name `pacman -T` says is already provided. Desktop apps already installed from a sync repo (CachyOS) are therefore neither rebuilt nor replaced.
|
||||
5. If anything is left and neither `yay` nor `paru` is installed, bootstraps `yay`: from a sync repo when `pacman -Si yay` finds one (CachyOS, chaotic-aur), otherwise by building `yay-bin` from the AUR with `makepkg -si` (installs `git` and `base-devel` first; skipped as root, since makepkg refuses root).
|
||||
6. Runs `yay -S --needed --noconfirm` on the remaining names, falling back to `paru`. yay prefers a sync repo over the AUR when both have the name. If no helper could be bootstrapped it prints `warning: no yay/paru and none could be bootstrapped — skipping AUR packages: …` and continues.
|
||||
|
||||
## 15-tools
|
||||
|
||||
`.chezmoiscripts/run_onchange_after_15-tools.sh.tmpl` installs the tools that do not come from pacman. It prepends `~/.cargo/bin` to `PATH`, since chezmoi's environment can predate the shell config that adds it. Any failing step aborts the script, so chezmoi keeps the old hash and retries on the next apply.
|
||||
|
||||
| Step | Condition | Action |
|
||||
| --- | --- | --- |
|
||||
| rustup | `cargo` not on `PATH` | `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \| sh -s -- -y --no-modify-path`. `-y` because there is no prompt under chezmoi; `--no-modify-path` because the shell rc files are symlinks into this repo and already source `~/.cargo/env` |
|
||||
| cargo crates | always | `cargo install --locked <crate>` for each `packages.shared.cargo` entry missing from `cargo install --list` |
|
||||
| mise tools | `mise` installed (warning otherwise) | `mise use -g <tools…>` for each `packages.shared.mise` entry absent from `mise ls --global --json`, then `mise install <tools…>`. A tool already in the global config keeps its pinned version. omp and opencode come from here |
|
||||
| old omp binary | `~/.local/bin/omp` is a regular file and `mise which omp` resolves | Deletes it: a copy from omp's own installer (`omp.sh/install`) sits ahead of mise's shims on `PATH` |
|
||||
| strata | roles `desktop`/`laptop`, no `~/.local/bin/strata` | `curl -fsSL https://raw.githubusercontent.com/lgse/strata/main/install.sh \| bash -s -- --with-desktop-entry --with-file-manager` |
|
||||
| strata file chooser | roles `desktop`/`laptop`, `~/.local/bin/strata` exists and `~/.local/share/xdg-desktop-portal/portals/strata.portal` does not | `strata --install-portal` |
|
||||
|
||||
The strata flags install the binary, `~/.local/share/applications/io.github.lgse.Strata.desktop`, and the `org.freedesktop.FileManager1` D-Bus service in `~/.local/share/dbus-1/services` ("Open file location" from other apps). The folder default (`inode/directory`) comes from the managed `~/.config/mimeapps.list` instead of `--with-folder-association`, because `xdg-mime default` would replace that symlink with a plain file. The installer's own dependency step runs `sudo -n pacman`, which fails without cached credentials, so its runtime packages are declared in `packages.arch.desktop.pacman` and are already present when it runs.
|
||||
|
||||
`strata --install-portal` makes strata the system file chooser for portal-aware apps (browsers, GTK file dialogs). It writes `strata.portal` and the `org.freedesktop.impl.portal.desktop.strata` D-Bus service under `~/.local/share`, prepends `strata;` to the `org.freedesktop.impl.portal.FileChooser` line of `~/.config/xdg-desktop-portal/<desktop>-portals.conf` (on Umbriel `umbriel-portals.conf`, copied from `/usr/share/xdg-desktop-portal/` when absent, so the compositor's backends stay as fallbacks), marks the in-app offer answered (`~/.config/strata/portal-opt-in-v1`), and restarts `xdg-desktop-portal.service`. That config file is strata's, not chezmoi's: `strata --uninstall-portal` restores it. Check: `gdbus call --session --dest org.freedesktop.impl.portal.desktop.strata --object-path /org/freedesktop/portal/desktop --method org.freedesktop.DBus.Properties.Get org.freedesktop.impl.portal.FileChooser version` prints `uint32 4`. Apps that draw their own picker instead of calling the portal are unchanged.
|
||||
|
||||
## 20-gh-extensions
|
||||
|
||||
`.chezmoiscripts/run_onchange_after_20-gh-extensions.sh.tmpl` installs each `packages.shared.gh_extensions` entry. It is a fish script (`#!/usr/bin/env fish`), so fish sources `~/.config/fish` first: `PATH` (mise, `~/.local/bin`, `~/.cargo/bin`) and `GITHUB_TOKEN` (from `gh auth token` in `conf.d/10-env.fish`) are those of a fish session. It runs after every file is applied and after [15-tools](#15-tools), so the fish config and `~/.cargo/env.fish` exist.
|
||||
|
||||
- Exits 0 with `warning: gh not installed — skipping extensions` if `gh` is missing.
|
||||
- Exits 0 with `warning: gh not authenticated — run 'gh auth login', then 'chezmoi apply' again` if `gh auth status` fails.
|
||||
- Reads `gh extension list`, then runs `gh extension install <repo>` only for extensions not already present.
|
||||
- Exits 1 listing the failed extensions if any install failed, so the next apply retries.
|
||||
|
||||
> [!WARNING]
|
||||
> A skipped run still exits 0, so chezmoi records this content as done and a plain re-apply does not retry it, despite what the warning text says. After `gh auth login`, either run `gh extension install` for each entry by hand, or clear chezmoi's onchange state with `chezmoi state delete-bucket --bucket=entryState` and apply again (this re-runs every `run_onchange_` script).
|
||||
|
||||
## 25-skills
|
||||
|
||||
`.chezmoiscripts/run_onchange_after_25-skills.sh.tmpl` installs the agent skills declared in [`skills.yaml`](data.md#skillsyaml) with the Skills CLI (`npx -y skills@1.7.0`). See [Skills](../ai/skills.md).
|
||||
|
||||
- Exits 0 with a warning if `npx` is missing.
|
||||
- One-time migration: deletes any directory in `~/.agents/skills` or `~/.claude/skills` that contains symlinks into the chezmoi source directory (the old vendored layout), and removes `~/.agents/.skill-lock.json` if it is a symlink into the source directory.
|
||||
- Removes skills listed in `${XDG_STATE_HOME:-~/.local/state}/chezmoi/skills.txt` that are no longer declared, with `skills remove -g -y`.
|
||||
- Runs one `skills add <source> -g -y -a <agents> --skill <names…>` per source entry. Output is captured; on failure the last 8 lines are printed.
|
||||
- Writes the declared names to `skills.txt`. Claude-only installs are not in the CLI's lock, so the script keeps its own record.
|
||||
- Exits 1 if any source failed, so chezmoi keeps the old hash and retries on the next apply.
|
||||
|
||||
## 30-user-services
|
||||
|
||||
`.chezmoiscripts/run_once_after_30-user-services.sh.tmpl` enables session-critical systemd user units.
|
||||
|
||||
- If `~/.config/systemd/user/kitty-day-save.service` exists (from `dot_config/systemd/user/`, so desktop/laptop roles only) and `systemctl` is available, runs `systemctl --user daemon-reload`, then `systemctl --user enable --now kitty-day-save.service`. Failures are ignored. See [kitty-day-save](../shell/scripts.md#kitty-day-save).
|
||||
- Same check for `~/.config/systemd/user/projects-sync.timer`, then `systemctl --user enable --now projects-sync.timer`. See [projects-sync](../shell/scripts.md#projects-sync).
|
||||
|
||||
Other personal automation units (brain-lens, omp-loop, rclone, hermes, voxtype) are deliberately not enabled; the script comment says to enable them by hand per machine. Their unit files are not in the repo, so they have to exist on the machine already.
|
||||
|
||||
## 40-post
|
||||
|
||||
`.chezmoiscripts/run_onchange_after_40-post.sh.tmpl` is post-apply housekeeping. It re-runs when the script is edited.
|
||||
|
||||
| Step | Condition | Action |
|
||||
| --- | --- | --- |
|
||||
| bat cache | `bat` installed | `bat cache --build` |
|
||||
| Font cache | `fc-cache` installed | `fc-cache -f` |
|
||||
|
||||
Each step ignores failures (`|| true`). tpm is cloned by [`.chezmoiexternal.toml`](externals-and-ignore.md#chezmoiexternaltoml), not here.
|
||||
|
||||
## build-bat-cache
|
||||
|
||||
`run_onchange_after_build-bat-cache.sh.tmpl` rebuilds bat's syntax and theme cache when the managed inputs change. It hashes `dot_config/bat/themes/cyberdream.tmTheme` and `dot_config/bat/config`, exits quietly if `bat` is missing, and otherwise runs `bat cache --build`. This registers the cyberdream theme and the PureScript syntax cloned by [`.chezmoiexternal.toml`](externals-and-ignore.md#chezmoiexternaltoml).
|
||||
|
||||
## install-yazi-plugins
|
||||
|
||||
`run_onchange_after_install-yazi-plugins.sh.tmpl` reinstalls yazi plugins whenever `~/.config/yazi/package.toml` changes (the template hashes it via `joinPath .chezmoi.homeDir`, guarded with `stat`). It exits quietly if `ya` is missing or `${XDG_CONFIG_HOME:-$HOME/.config}/yazi/package.toml` does not exist, and otherwise runs `ya pkg install`. If that fails, it deletes ya's clone cache (`${XDG_CACHE_HOME:-$HOME/.cache}/yazi/packages`), since clones made by an older ya can break a newer one, and retries once. `~/.config/yazi` is the [prdlk/yazi](https://github.com/prdlk/yazi) external checkout, so the plugins land in its `plugins/`, which that repo's `.gitignore` excludes apart from the vendored `bunny.yazi`. The plugin list is on the [yazi page](../tools/yazi.md).
|
||||
@@ -0,0 +1,53 @@
|
||||
# Desktop
|
||||
|
||||
The desktop layer on CachyOS: the Umbriel compositor, the Noctalia shell, fontconfig, and default applications.
|
||||
|
||||
> [!NOTE]
|
||||
> **Role gating**
|
||||
>
|
||||
> `.chezmoiignore.tmpl` skips the desktop layer unless the machine runs Linux **and** its role is `desktop` or `laptop`. The gated targets are `~/.config/umbriel`, `~/.config/noctalia`, `~/.config/fontconfig`, `~/.config/mimeapps.list`, `~/.config/xdg-terminals.list`, `~/.config/systemd`, `~/.local/bin/agent-root`, `~/.local/bin/kitty-day-save`, `~/.local/bin/umbriel-launch`, `~/.local/bin/umbriel-smart-move`, `~/.local/bin/umbriel-float-square`, `~/.local/bin/cp-appid`, and `~/.local/bin/projects-sync`. On the `server` role these are not applied.
|
||||
|
||||
The session is [Umbriel](https://docs.noctalia.dev/umbriel/), Noctalia's scrolling compositor, with Noctalia as the only session process: it provides the bar, launcher, clipboard history, notifications, polkit agent, and OSD.
|
||||
|
||||
| Page | Covers |
|
||||
| --- | --- |
|
||||
| [Umbriel](umbriel.md) | `~/.config/umbriel` (session, layout, outputs, rules, scratchpads, binds), `umbriel-launch`, `umbriel-smart-move`, and `umbriel-float-square` |
|
||||
| [Fonts and default apps](fonts-and-apps.md) | `~/.config/fontconfig/fonts.conf`, `~/.config/mimeapps.list`, the Victor Mono Nerd Font packages |
|
||||
|
||||
The Noctalia config itself (`~/.config/noctalia`) is the [prdlk/noctalia](https://github.com/prdlk/noctalia) external, see [externals](../chezmoi/externals-and-ignore.md#chezmoiexternals).
|
||||
|
||||
## Packages
|
||||
|
||||
`.chezmoidata/packages.yaml` declares the desktop packages under `packages.arch.desktop`. `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` installs them only when the role is `desktop` or `laptop`:
|
||||
|
||||
| Source | Packages |
|
||||
| --- | --- |
|
||||
| pacman | `noctalia` (the CachyOS build where that repo is configured, else `extra`), `polkit`, `xdg-user-dirs`, `xdg-terminal-exec`, Wayland utilities (`wl-clipboard`, `cliphist`, `wtype`, `ydotool`, `grim`, `slurp`, `pamixer`, `brightnessctl`, `libnotify`), the bound apps (`nautilus`, `imv`, `mpv`, `evince`, `chromium`, `obsidian`), and strata's runtime deps |
|
||||
| AUR via `yay`/`paru` (a sync repo wins when it has the name) | `tldraw-offline-bin`, `bibata-cursor-translucent`, `umbriel-git` (with `xdg-desktop-portal-umbriel-git`), `herdr-bin`, `voxtype-bin`, and the desktop apps `beeper-bin`, `fastmail`, `1password`, `brave-origin-bin`, `claude-desktop`, `spotify` |
|
||||
|
||||
AUR names that an installed package already provides are skipped; with no `yay` or `paru` the script bootstraps `yay` first. strata, the default file manager, comes from its own installer in [15-tools](../chezmoi/scripts.md#15-tools). See [chezmoi scripts](../chezmoi/scripts.md) for the full install flow and [packages.yaml](../chezmoi/data.md#packagesyaml) for the complete lists.
|
||||
|
||||
## Thunderbolt eGPU (compute only)
|
||||
|
||||
The RTX 5060 Ti in a USB4 enclosure (Minisforum `TBGAA`, bolt policy `iommu`) is for CUDA only: AI models, never displays or rendering. Every display hangs off the Radeon 8060S iGPU (PCI `c4:00.0`). Packages: `linux-cachyos-nvidia-open`, `nvidia-utils`, `cuda`, `opencl-nvidia`. Nothing in this section is managed by chezmoi except the session environment.
|
||||
|
||||
| File | Setting | Why |
|
||||
| --- | --- | --- |
|
||||
| `/etc/default/limine` | no `pci=nommconf`, no `pcie_aspm=off`; `pcie_aspm.policy=performance` instead | The first two make Linux skip the PCIe `_OSC` control request (`not requesting OS control; OS requires [ExtendedConfig ASPM …]`). Then `pciehp` never binds the USB4 bridges `00:01.1`/`00:01.2`, and the enclosure authorizes while the GPU never shows up in `lspci`. `pcie_aspm.policy=performance` disables ASPM without that side effect. Run `limine-update` after editing. |
|
||||
| `/etc/modprobe.d/nvidia-egpu.conf` | `install nvidia_drm /bin/true`, `install nvidia_modeset /bin/true`, `NVreg_DynamicPowerManagement=0x00`, `NVreg_PreserveVideoMemoryAllocations=0` | Without `nvidia_drm` there is no `/dev/dri` node for the eGPU, so no compositor, GBM, or VA-API driver can open it. CUDA needs only `nvidia` and `nvidia_uvm`. `nvidia-suspend.service` is not enabled, and with `PreserveVideoMemoryAllocations=1` suspend fails while the eGPU is bound. |
|
||||
| `/etc/modprobe.d/thunderbolt-egpu.conf` | `options thunderbolt host_reset=0 clx=0` | `host_reset=0` keeps the firmware-built PCIe tunnel at boot; with the default reset the tunnel drops while `nvidia` initialises the card (Xid 79, GPU stuck until reboot). `clx=0` keeps the USB4 lanes out of CL1/CL2 low-power states. |
|
||||
| `/etc/systemd/system/nvidia-egpu-powercap.service`, `/etc/udev/rules.d/62-nvidia-egpu-powercap.rules` | `nvidia-smi -pm 1 -pl 150 -lgc 210,1800` when the `nvidia` module loads | Caps the card at 150 W / 1800 MHz before anything can load it. |
|
||||
| `/etc/udev/rules.d/61-nvidia-compute.rules` | `nvidia-modprobe -c0 -u` when the `nvidia` module loads | `nvidia-utils`' own rule creates `/dev/nvidia*` when `nvidia_drm` loads, and that module never loads here. |
|
||||
| no `/etc/modules-load.d` entry | none | udev loads `nvidia` by modalias when the GPU reaches the bus, at boot or on hotplug. |
|
||||
|
||||
`nouveau` is blacklisted by `nvidia-utils` (`/usr/lib/modprobe.d/nvidia-utils.conf`). Without that blacklist, nouveau binds the card, the NVIDIA driver reports `GPU … is already bound to nouveau`, and a link drop deadlocks `pciehp` in nouveau's GSP teardown.
|
||||
|
||||
Session side: `conf.d/10-env.fish` sets `__EGL_VENDOR_LIBRARY_FILENAMES` (Mesa) and `VK_DRIVER_FILES` (RADV), see [fish](../shell/fish.md#10-envfish). Umbriel starts through a fish login shell, so the compositor, Noctalia, and every app inherit them. Without them, glvnd and the Vulkan loader load the NVIDIA vendor libraries into every process, and those processes hold `/dev/nvidiactl`.
|
||||
|
||||
Never expose `nvidia_icd.json` to LM Studio. When its engine survey reaches the eGPU through NVIDIA's Vulkan driver, the card falls off the bus (`Xid 79`) and the machine hard-resets. The survey runs when the app starts and when the `llmster` daemon starts, and any `lms` command starts that daemon.
|
||||
|
||||
Check: `nvidia-smi` lists the card, and `fuser -v /dev/nvidia*` shows only CUDA processes. A short CUDA kernel (vector add) runs correctly.
|
||||
|
||||
### Status: unusable under load on the Flow Z13 (GZ302EA, BIOS 311)
|
||||
|
||||
The moment a model starts inference on the eGPU, the platform hard-resets with `x86/amd: Previous system reset reason [0x08000800]: an uncorrected error caused a data fabric sync flood event`, no Xid, no log tail. Firmware grants no AER/DPC to the OS, so a tunnel fault under load becomes a full reset. Everything above was tested one variable at a time on 2026-10-17 and none of it changed the outcome: CUDA or Vulkan engine, model size (27B and 12B), both USB4 ports, dock wiring per the DEG2 manual on a 650 W PSU, ASPM off, `clx=0`, 150 W power cap, KMS loaded or not, and the previous OS install with stock NVIDIA config. The same models run fine on the iGPU with the dock unplugged, so the fault is the Z13's USB4 PCIe path under GPU load, not the SoC alone. Other GZ302EA owners report the same reset code under sustained load ([Level1Techs](https://forum.level1techs.com/t/strix-halo-hard-power-off-under-sustained-inference-load-rog-flow-z13-gz302ea-instrumented-root-cause-hunt-ec-cooling-gated-power-cut-a-data-fabric-sync-flood-can-framework-desktop-other-strix-halo-owners-try-to-repro/252607)) and eGPU trouble on this platform ([ROG forum](https://rog-forum.asus.com/t5/rog-flow-series/gz302ea-bios-311-tb4-egpu-permanently-fails-on-linux-after/td-p/1146714)). Untested: a shorter certified 40 Gbps cable, the same dock and card on another host, Windows. LLM work runs on the 8060S (Vulkan/ROCm, unified memory) until one of those changes the picture.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Fonts and default apps
|
||||
|
||||
Fontconfig monospace override to Victor Mono Nerd Font, the Nerd Font packages, and the XDG default-application table.
|
||||
|
||||
Two small files set system-wide defaults: `fonts.conf` picks the monospace font, and `mimeapps.list` picks which application opens each file type and URL scheme.
|
||||
|
||||
> [!NOTE]
|
||||
> Linux `desktop` and `laptop` roles only. `.chezmoiignore.tmpl` skips `~/.config/fontconfig/**` and `~/.config/mimeapps.list` on other roles.
|
||||
|
||||
## Fontconfig
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_config/fontconfig/fonts.conf` | `~/.config/fontconfig/fonts.conf` |
|
||||
|
||||
**~/.config/fontconfig/fonts.conf**
|
||||
|
||||
```xml
|
||||
<fontconfig>
|
||||
<match target="pattern">
|
||||
<test name="family" qual="any">
|
||||
<string>monospace</string>
|
||||
</test>
|
||||
<edit name="family" mode="prepend_first" binding="strong">
|
||||
<string>VictorMono Nerd Font</string>
|
||||
</edit>
|
||||
</match>
|
||||
</fontconfig>
|
||||
```
|
||||
|
||||
Any font request that includes the generic `monospace` family gets `VictorMono Nerd Font` added to the front of its family list (`prepend_first`) with strong binding. Apps that ask for "monospace" therefore render in Victor Mono with Nerd Font glyphs, and fall back through fontconfig's normal monospace list if a glyph is missing. The file does not configure sans/serif families, hinting, antialiasing, or subpixel rendering. Those stay at system defaults.
|
||||
|
||||
Some tools set their own fonts. The terminals set fonts in their own configs (see [kitty](../terminal/kitty.md)).
|
||||
|
||||
### Font packages
|
||||
|
||||
Victor Mono Nerd Font is installed on every role (including `server`) by `run_onchange_before_10-packages.sh.tmpl` from `.chezmoidata/packages.yaml`, not only on desktop roles:
|
||||
|
||||
Package: `ttf-victor-mono-nerd`, from pacman (`packages.arch.pacman`).
|
||||
|
||||
`fonts.conf` is desktop/laptop only; the font package is also installed on `server`.
|
||||
|
||||
## Default applications
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_config/mimeapps.list` | `~/.config/mimeapps.list` |
|
||||
|
||||
`mimeapps.list` is the XDG file that `xdg-open`, GTK/Qt file dialogs, and browsers use to choose a handler. `[Default Applications]` sets the preferred handler. `[Added Associations]` adds entries to "Open with" lists.
|
||||
|
||||
### Default applications
|
||||
|
||||
| Group | MIME types / schemes | Handler (`.desktop`) |
|
||||
| --- | --- | --- |
|
||||
| Text and scripts | `text/plain`, `text/x-shellscript`, `text/x-python`, `text/x-script.python` | `vim.desktop` |
|
||||
| Notes | `text/markdown`, `application/x-obsidian-canvas`, `application/x-obsidian-base`, `x-scheme-handler/obsidian` | `obsidian.desktop` (`obsidian` from `packages.arch.desktop.pacman`) |
|
||||
| Web pages | `text/html`, `application/xhtml+xml`, `application/x-extension-htm`, `-html`, `-shtml`, `-xhtml`, `-xht` | `brave-origin.desktop` |
|
||||
| Web links | `x-scheme-handler/http`, `https`, `chrome` | `brave-origin.desktop` |
|
||||
| Browser internals | `x-scheme-handler/about`, `unknown` | `chromium.desktop` |
|
||||
| PDF | `application/pdf` | `brave-origin.desktop` |
|
||||
| Directories | `inode/directory` | `io.github.lgse.Strata.desktop` ([strata](../chezmoi/scripts.md#15-tools), which also registers the `org.freedesktop.FileManager1` service for "Open file location" and serves the portal file chooser) |
|
||||
| Mail | `x-scheme-handler/mailto`, `fastmail` | `com.fastmail.Fastmail.desktop` (`fastmail` from `packages.arch.desktop.aur`) |
|
||||
| Windows executables | `application/vnd.microsoft.portable-executable` | `wine.desktop` |
|
||||
| AI tools | `x-scheme-handler/claude` | `com.anthropic.Claude.desktop` |
|
||||
| | `x-scheme-handler/claude-cli` | `claude-code-url-handler.desktop` |
|
||||
| | `x-scheme-handler/opencode` | `ai.opencode.desktop.desktop` |
|
||||
| | `x-scheme-handler/antigravity` | `antigravity.desktop` |
|
||||
| | `x-scheme-handler/piebald` | `piebald-handler.desktop` |
|
||||
| | `x-scheme-handler/openhuman` | `OpenHuman-handler.desktop` |
|
||||
| Productivity | `x-scheme-handler/todoist`, `com.todoist` | `todoist.desktop` |
|
||||
| | `x-scheme-handler/notion` | `notion.desktop` |
|
||||
| | `x-scheme-handler/anytype` | `anytype.desktop` |
|
||||
| | `x-scheme-handler/tolaria` | `tolaria-handler.desktop` |
|
||||
| | `x-scheme-handler/tldraw-offline` | `tldraw-offline.desktop` |
|
||||
| | `x-scheme-handler/figma` | `figma-linux.desktop` |
|
||||
| Chat | `x-scheme-handler/slack` | `slack.desktop` |
|
||||
| | `x-scheme-handler/discord-647066573147996160` | `discord-647066573147996161.desktop` |
|
||||
| Feeds | `x-scheme-handler/follow`, `folo` | `folo.desktop` |
|
||||
|
||||
### Added associations
|
||||
|
||||
| MIME types / schemes | Also offered |
|
||||
| --- | --- |
|
||||
| `http`, `https`, `chrome` schemes; `text/html`, `application/xhtml+xml`, and the `x-extension-*` web types | `brave-origin.desktop`, `userapp-Floorp-8MTFP3.desktop` |
|
||||
| `x-scheme-handler/mailto` | `userapp-Evolution-PCEYQ4.desktop` |
|
||||
| `application/vnd.microsoft.portable-executable` | `wine.desktop` |
|
||||
| `application/pdf` | `brave-origin.desktop` |
|
||||
| `x-scheme-handler/opencode` | `ai.opencode.desktop.desktop` |
|
||||
|
||||
The added mail association is Evolution, but the default mail handler is the Fastmail app.
|
||||
|
||||
> [!TIP]
|
||||
> In symlink mode, `~/.config/mimeapps.list` is a symlink into the repo. When `xdg-mime default` or a "set as default" dialog updates it, the tool either writes through the symlink and edits the repo file directly, or replaces the symlink with a regular file. Check `git status` or `chezmoi status` after changing defaults.
|
||||
|
||||
## Related
|
||||
|
||||
- [Umbriel](umbriel.md): the compositor these defaults serve.
|
||||
- [Installation](../installation.md): role selection that decides whether this layer applies.
|
||||
@@ -0,0 +1,184 @@
|
||||
# Umbriel
|
||||
|
||||
Umbriel scrolling compositor config — Noctalia-only session, layout and springs, named scratchpads, outputs, and keybindings.
|
||||
|
||||
[Umbriel](https://docs.noctalia.dev/umbriel/) is a wlroots scrolling compositor from the Noctalia project. The config sets up the layout, window rules, springs, binds, and per-app scratchpads. The session starts only `noctalia`, which provides the launcher, clipboard history, polkit agent, notifications, and OSD.
|
||||
|
||||
Noctalia's polkit agent is off by default. `~/.config/noctalia/config.toml` (the [prdlk/noctalia](https://github.com/prdlk/noctalia) external, see [externals](../chezmoi/externals-and-ignore.md#chezmoiexternals); merged under the GUI's unmanaged `~/.local/state/noctalia/settings.toml`) sets `[shell] polkit_agent = true`, so `pkexec` and other privileged actions prompt in a Noctalia panel. `polkitd` itself is D-Bus activated (`polkit.service` is static; nothing to enable).
|
||||
|
||||
> [!NOTE]
|
||||
> `desktop` and `laptop` roles only (`.chezmoiignore.tmpl`). `umbriel-git` comes from the AUR (`packages.arch.desktop.aur`) and pulls in its portal backend `xdg-desktop-portal-umbriel-git` (screenshots and screen sharing for portal clients). The `[cachyos]` `umbriel` build (`0.1.0.r1213`) lacks `window-move-by`, which `binds.toml` uses, and rejects the config. `umbriel-git` conflicts with it, so on a machine that already has the repo build, swap them by hand once (`yay -S umbriel-git`, accepting the removal); the package script runs `--noconfirm`, which declines that removal.
|
||||
|
||||
## Files
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_config/umbriel/config.toml` | `~/.config/umbriel/config.toml`: includes, session, environment, input, layout, appearance, animation |
|
||||
| `dot_config/umbriel/outputs.toml` | `~/.config/umbriel/outputs.toml` |
|
||||
| `dot_config/umbriel/rules.toml` | `~/.config/umbriel/rules.toml`: scratchpads, window and layer rules |
|
||||
| `dot_config/umbriel/binds.toml` | `~/.config/umbriel/binds.toml` |
|
||||
| `dot_local/bin/executable_umbriel-launch` | `~/.local/bin/umbriel-launch` |
|
||||
| `dot_local/bin/executable_umbriel-smart-move` | `~/.local/bin/umbriel-smart-move` |
|
||||
| `dot_local/bin/executable_umbriel-float-square` | `~/.local/bin/umbriel-float-square` |
|
||||
|
||||
Umbriel watches these files and applies a valid save immediately; `umbriel config validate` checks them without a session. Include paths resolve against the symlinks in `~/.config/umbriel`, not the chezmoi source. Defining `[keybinds]` replaces Umbriel's built-in binds, so `binds.toml` is the complete set.
|
||||
|
||||
### Theme
|
||||
|
||||
`~/.config/umbriel/noctalia.toml` holds the colors. Noctalia's umbriel template writes it on every theme change and its post-hook keeps `"noctalia.toml"` last in `[include.optional]`; the file is not managed by chezmoi.
|
||||
|
||||
## Settings
|
||||
|
||||
| Area | Value |
|
||||
| --- | --- |
|
||||
| Keyboard | `us`, repeat delay 400 ms, rate 50, NumLock on |
|
||||
| Touchpad | natural scroll, tap, left-right-middle, sensitivity 1.0 |
|
||||
| Tablet, touch | mapped to `eDP-1` |
|
||||
| Cursor | `Bibata_Ghost` (dark translucent Bibata, AUR `bibata-cursor-translucent`) 28, hidden while typing, follows keyboard focus (so scratchpads open on the output focus moved to). Umbriel exports it as `XCURSOR_THEME` and over XSETTINGS; GTK apps on Wayland read the GNOME settings instead, set once with `gsettings set org.gnome.desktop.interface cursor-theme Bibata_Ghost` and `… cursor-size 28` (dconf, not managed) |
|
||||
| Layout | gap 8, presets 0.333 / 0.5 / 0.667, new columns 1/3 (0.8 on the laptop panel, see [Outputs](#outputs)), focus never centers, a lone column centers |
|
||||
| Appearance | corner radius 10, no shadows, wallpaper-only blur on every window (radius 10, 4 passes, strong enough to hide wallpaper detail behind Brave Origin's own translucent background), tab bar hidden for one tab |
|
||||
| Activation | apps may focus and reveal their windows (`focus_on_activate`), e.g. Noctalia notification actions |
|
||||
| Overview | zoom 0.4; no hot corner |
|
||||
| Animation | springs `0.82,1125` (open), `0.88,2025` (close), `0.85,1463` (move, overview, scratchpad), `0.78,1350` (workspaces) |
|
||||
| Environment | `TERMINAL=ghostty` (default terminal), `ELECTRON_OZONE_PLATFORM_HINT=auto`, `QT_QPA_PLATFORM=wayland`, `QT_QPA_PLATFORMTHEME=kde`, `QT_STYLE_OVERRIDE=Darkly` |
|
||||
|
||||
Umbriel owns `XDG_CURRENT_DESKTOP`, so the config does not override it.
|
||||
|
||||
## Outputs
|
||||
|
||||
Matched by monitor identity (`umbriel outputs` → `Config name`). The external scales bring each display to about 110 logical pixels per inch (the ultrawide's native density), so windows keep their size across them. The laptop panel runs at 2 (112 logical ppi, 1280x800). It sits at the origin and every external sits directly to its left at negative x, bottoms aligned. The externals are used one at a time and share that slot.
|
||||
|
||||
| Display | Physical | Scale | Logical | Position | Other |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Laptop (Tianma TL134ADXP03, `eDP-1`) | 2560x1600, 224 ppi | 2 | 1280x800 | `0, 0` | VRR in fullscreen, cursor starts here, new scrolling columns 0.8 wide |
|
||||
| Apple Studio Display | 5120x2880, 218 ppi | 2 | 2560x1440 | `-2560, -640` | |
|
||||
| Samsung S34CG50 ultrawide | 3440x1440@100, 110 ppi | 1 | 3440x1440 | `-3440, -640` | VRR in fullscreen, workspaces 1–9 start in dwindle |
|
||||
| Portable HDMI (AOP 16PM1QB) | 1920x1080, 141 ppi, rotated 90° | 1.25 | 864x1536 | `-864, -736` | |
|
||||
| Portable USB-C (ASUS MB16AC) | 1920x1080, 141 ppi | 1.25 | 1536x864 | `-1536, -64` | |
|
||||
|
||||
Umbriel sets the layout mode per workspace, not per output, so the ultrawide's dwindle default is nine `[[workspace]]` rules in `outputs.toml`, one for each index 1–9 on that monitor. A workspace past 9 starts in scrolling. `Mod+W` still toggles any workspace between scrolling and dwindle.
|
||||
|
||||
The Studio Display's phantom 640x480 connector needs no rule: Umbriel leaves connectors without a preferred mode or identity disabled.
|
||||
|
||||
## Scratchpads
|
||||
|
||||
Named scratchpads hold one app each. A window rule sends each app into its scratchpad when it opens; toggling an empty scratchpad runs `spawn_when_empty`. A shown scratchpad window floats centered on the output under the pointer at 48% × 77% of its usable area (`default_floating_size`, the last window rule in `rules.toml`: 1638×1109 on the ultrawide, 614×594 on the laptop panel, 1229×1109 on the Studio Display) over a desktop dimmed to 0.6. `animation.scratchpad.maximize` is off. The size is a default: a resident that was already open before the rule changed keeps its old geometry until it is reopened.
|
||||
|
||||
| Scratchpad | App id | Launch | Keys |
|
||||
| --- | --- | --- | --- |
|
||||
| `tern` | `so.stencil.tern` | `tern` | `Super+A`, `Super+Alt+Ctrl+H` |
|
||||
| `claude` | `com.anthropic.Claude` | `claude-desktop` | `Super+Alt+Ctrl+L`, `Super+Ctrl+A` |
|
||||
| `fastmail` | `com.fastmail.Fastmail` / `com-fastmail-fastmail` | `gtk-launch com.fastmail.Fastmail` | `Super+Alt+Ctrl+E` |
|
||||
| `beeper` | `Beeper` | `gtk-launch beeper` | `Super+Alt+Ctrl+M`, `Super+Alt+Ctrl+B` |
|
||||
| `home` | `brave-home.google.com__*` | `gtk-launch 'Google Home'` | `Super+Ctrl+H` |
|
||||
| `lmstudio` | `ai.elementlabs.lmstudio` | `gtk-launch ai.elementlabs.lmstudio` | `Super+Ctrl+L` |
|
||||
|
||||
## Window rules
|
||||
|
||||
| Match | Rule |
|
||||
| --- | --- |
|
||||
| every window | blur |
|
||||
| `Folo`, `Spotify`, `zen`, `brave-origin`, `gemini-desktop`, `claude-desktop`, `kitty*`, `zed`, `obsidian`, `Beeper`, `chromium`, `com.mitchellh.ghostty` | opacity 0.9, tiled, focused |
|
||||
| `brave-origin`, `zen`, `chromium` | opacity 0.97 (overrides the 0.9 above) |
|
||||
| `dev.noctalia.Noctalia` | floating 1080 × 920 |
|
||||
| `dev.noctalia.UmbrielSharePicker` | floating 800 × 600 |
|
||||
| `io.github.lgse.Strata`, `org.gnome.Nautilus`, `com.gabm.satty` | floating, half the output wide, focused |
|
||||
| `brave-youtube.com__*` | floating, pinned, 30% × 30%, 10 px from the bottom-left |
|
||||
| title `Picture-in-Picture` / `Picture in picture` | floating, unfocused, opaque, 24 px from the top-right of `eDP-1` |
|
||||
| YouTube and chat/mail/dashboard apps (`chromium`, Calendar, Discord, Slack, Gemini, Hermes, Todoist, Cloudflare web apps) | laptop panel, workspace 1, maximized |
|
||||
| `zed`, `obsidian`, sticky notes, `vacuumtube`, `Fladder`, `kitty`, `kitty-herdr`, `kitty-tmux` | Apple Studio Display |
|
||||
| the six scratchpad apps | their scratchpad, floating 48% × 77% centered |
|
||||
|
||||
Noctalia's bar, panels, notifications, dock, OSD, and desktop widgets get blur through a layer rule.
|
||||
|
||||
## Keybindings
|
||||
|
||||
`Mod` is Super.
|
||||
|
||||
### Shell and session
|
||||
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| `Mod+Space` | Noctalia launcher |
|
||||
| `Mod+Shift+Space` | Noctalia launcher, emoji search (`/emo`) |
|
||||
| `Super+Ctrl+Shift+BackSpace` | Noctalia launcher, session actions (`/session`: lock, suspend, reboot, shutdown, logout) |
|
||||
| `Mod+P` | Noctalia clipboard history |
|
||||
| `Mod+Period` / `Mod+Comma` | Noctalia control center / settings |
|
||||
| `Super+Alt+Ctrl+N` | Noctalia notes panel |
|
||||
| `Mod+Shift+B` | Noctalia bookmarks panel (`dunarand/bookmarks` plugin) |
|
||||
| `Mod+Ctrl+R` | ostt recorder plugin |
|
||||
| `Mod+Tab` | overview |
|
||||
| `Mod+Shift+Q` | Noctalia session menu |
|
||||
| `Mod+Shift+E` | quit Umbriel (asks first) |
|
||||
| `Mod+Escape` | pass shortcuts through to the focused app (works while inhibited) |
|
||||
| `Mod+S`, `Print` | region screenshot |
|
||||
| `Mod+Ctrl+S`, `Ctrl+Print` | focused-monitor screenshot |
|
||||
| `Mod+Ctrl+Shift+S` | all-monitors screenshot |
|
||||
| `Mod+F24` | voxtype dictation (`--profile hermes`) |
|
||||
| media, volume, brightness keys | `noctalia msg …`, also on the lock screen |
|
||||
|
||||
### Apps
|
||||
|
||||
`umbriel-launch PATTERN COMMAND…` focuses the next window whose app id matches `PATTERN` (case-insensitive), cycling on repeated presses, and runs `COMMAND` through `umbriel msg spawn` when none is open. It skips scratchpad windows. The binds call it as `~/.local/bin/umbriel-launch` because the session `PATH` has no `~/.local/bin`.
|
||||
|
||||
| Keys | Cycles | Launches |
|
||||
| --- | --- | --- |
|
||||
| `Super+Alt+Ctrl+J` | Ghostty, kitty, alacritty | `ghostty` |
|
||||
| `Super+Alt+Ctrl+K` | browsers: Brave Origin, Chromium, Firefox (not web apps) | `brave-origin` |
|
||||
| `Super+Alt+Ctrl+G` | Ghostty | `ghostty` |
|
||||
| `Super+Alt+Ctrl+O` | Obsidian | `obsidian` |
|
||||
| `Super+Alt+Ctrl+F` | strata | `gtk-launch io.github.lgse.Strata` |
|
||||
| `Super+Alt+Ctrl+W` | tldraw offline | `gtk-launch tldraw-offline` |
|
||||
| `Super+Alt+Ctrl+T` | Todoist | `gtk-launch todoist` |
|
||||
| `Super+Alt+Ctrl+Y` | web apps (`webapp-*`, `chrome-*`, `brave-*__*`) | `gtk-launch YouTube` |
|
||||
| `Super+Alt+Ctrl+Space` | Sticky Notes Canvas | `gtk-launch io.github.faridjaff.StickyNotesCanvas` |
|
||||
| `Super+Ctrl+G` | Gemini | `gemini-desktop` |
|
||||
| `Mod+Shift+G` | — | Goliath kitty (`--class=kitty-goliath`) |
|
||||
|
||||
### Windows and layout
|
||||
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| `Mod+H` / `Mod+L` | focus left / right, or the output on that side at the strip's edge |
|
||||
| `Mod+J` / `Mod+K` | focus down / up, or the next / previous workspace at the edge |
|
||||
| `Mod+BackSpace` | previously focused window |
|
||||
| `Mod+WheelUp` / `Mod+WheelDown` | focus left / right |
|
||||
| `Mod+Left` / `Mod+Right` / `Mod+Up` | focus the output in that direction |
|
||||
| `Mod+O`, `Super+Ctrl+J` / `Super+Ctrl+K` | next / previous output |
|
||||
| `Mod+Shift+H` / `Mod+Shift+L` | [smart move](#smart-move) left / right: consume or expel, then on to the next output |
|
||||
| `Super+Ctrl+Shift+H` / `Super+Ctrl+Shift+L` | move column first / last |
|
||||
| `Mod+Shift+J` / `Mod+Shift+K` | move column to next / previous workspace |
|
||||
| `Mod+Shift+Down` / `Mod+Shift+Up` | move column to the right / left output |
|
||||
| `Mod+Shift+O`, `Mod+Ctrl+Shift+J` / `Mod+Ctrl+Shift+K` | move window to next / previous output |
|
||||
| `Mod+R` / `Mod+Shift+R` | cycle width / height presets |
|
||||
| `Alt+Shift+H` / `Alt+Shift+L` | width −10% / +10% |
|
||||
| `Alt+Shift+J` / `Alt+Shift+K` | height −10% / +10% |
|
||||
| `Alt+H` / `Alt+J` / `Alt+K` / `Alt+L` | move the focused floating window 50 px left / down / up / right (`window-move-by`, held keys repeat; kept inside the usable area) |
|
||||
| `Mod+F` | full-width column |
|
||||
| `Mod+Shift+F` | maximize to the output edges |
|
||||
| `Mod+Ctrl+F` | fullscreen |
|
||||
| `Mod+C` | floating window: [square it](#float-square) at the usable height minus 4 px top and bottom, centered; tiled: center column |
|
||||
| `Mod+T` | toggle floating |
|
||||
| `Mod+W` | toggle the workspace between the scrolling and dwindle layouts |
|
||||
| `Mod+G` | tab the focused window with the windows stacked in its column, or stack a tab group again (scrolling layout; dwindle has no columns). Windows join a group by dragging onto its tab bar |
|
||||
| `Mod+Q` | close window |
|
||||
| `Mod+1`…`Mod+9`, `Alt+Q W E R Y U I O` | workspace 1–9 / 1–8 |
|
||||
| `Mod+Shift+1`…`9`, `Alt+Shift+Q`…`O` | move window to that workspace |
|
||||
|
||||
Workspaces are dynamic per output.
|
||||
|
||||
### Smart move
|
||||
|
||||
`umbriel-smart-move left|right` consumes or expels the window left or right and continues across outputs. In the scrolling layout it picks one action for the focused window:
|
||||
|
||||
| Focused window | Action |
|
||||
| --- | --- |
|
||||
| stacked with other windows in its column | expel it to a new column on that side |
|
||||
| alone, with a column on that side | consume it into that column |
|
||||
| alone, at the strip's end | move it to the output on that side (nothing at the outermost output) |
|
||||
|
||||
Two presses step a window past its neighbour: the first stacks it into the neighbour's column, the second expels it on the far side. On dwindle and master workspaces it runs `window-move-or-output-left/right`. Floating and scratchpad windows stay put.
|
||||
|
||||
### Float square
|
||||
|
||||
`umbriel-float-square` (`Mod+C`) acts on the window with keyboard focus (`.active` in `umbriel windows --json`). A floating window becomes a square whose side is the output's usable height (below Noctalia's bar) minus 4 px above and below (`PAD`): `window-set-secondary-extent` and `window-set-primary-extent` get that side as a fraction of the usable height and width. The script then waits up to 1 s for the client to commit the new width, because `window-center` centers the committed size, and centers it. On the laptop panel that is 764×764 at y 32; on the ultrawide 1404×1404. A tiled window gets `column-center`, the previous `Mod+C`.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Installation
|
||||
|
||||
Install chezmoi and apply the CachyOS dotfiles with one command.
|
||||
|
||||
## One-liner
|
||||
|
||||
```sh
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply https://prad.codes/env/cachyos.git
|
||||
```
|
||||
|
||||
[get.chezmoi.io](https://get.chezmoi.io) is chezmoi's official install script. It installs the `chezmoi` binary into `./bin` of the current directory by default (pass `-b ~/.local/bin` before `--` to change that), then runs `chezmoi init --apply` with the repo URL. chezmoi clones the repo to `~/.local/share/chezmoi` and initializes its submodules (`dot_config/fish`) as part of `init`.
|
||||
|
||||
## Before you run it
|
||||
|
||||
### 1. Package manager
|
||||
|
||||
`sudo` for pacman. AUR packages (`topgrade`, `portop`, and the desktop apps on desktop roles) go through `yay` or `paru`; when neither is installed the package script installs `yay` from a sync repo that ships it (CachyOS) or builds `yay-bin` from the AUR.
|
||||
|
||||
### 2. age key
|
||||
|
||||
chezmoi is configured for age encryption with the identity at `~/.config/chezmoi/key.txt`. The key is never committed. Copy it from an existing machine before applying:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/chezmoi
|
||||
scp <existing-machine>:.config/chezmoi/key.txt ~/.config/chezmoi/key.txt
|
||||
chmod 600 ~/.config/chezmoi/key.txt
|
||||
```
|
||||
|
||||
### 3. Repo access
|
||||
|
||||
The repo at `https://prad.codes/env/cachyos` is public, so HTTPS needs no credentials. To clone over SSH instead, add the machine's key to the Gitea account and use `git@prad.codes:env/cachyos.git`.
|
||||
|
||||
## Options
|
||||
|
||||
Arguments after `--` go to chezmoi:
|
||||
|
||||
**SSH**
|
||||
|
||||
```sh
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- init --apply git@prad.codes:env/cachyos.git
|
||||
```
|
||||
|
||||
**Dry-run**
|
||||
|
||||
```sh
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- init https://prad.codes/env/cachyos.git
|
||||
chezmoi diff
|
||||
chezmoi apply
|
||||
```
|
||||
|
||||
**Install directory**
|
||||
|
||||
```sh
|
||||
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b ~/.local/bin init --apply https://prad.codes/env/cachyos.git
|
||||
```
|
||||
|
||||
**Working on the repo**
|
||||
|
||||
The checkout used day to day lives at `~/Code/prad.codes/env/cachyos`. chezmoi expects its source directory at `~/.local/share/chezmoi`, so either clone into that path or point chezmoi at the checkout:
|
||||
|
||||
```sh
|
||||
chezmoi init --source ~/Code/prad.codes/env/cachyos
|
||||
```
|
||||
|
||||
Both give the same model: one git checkout is the chezmoi source, and `mode = "symlink"` links files in `$HOME` back to it.
|
||||
|
||||
## First apply
|
||||
|
||||
1. **Prompts**: `.chezmoi.toml.tmpl` asks once for **name**, **email**, and **role** (`desktop`, `laptop`, `server`; the default is `desktop`). Answers are stored in `~/.config/chezmoi/chezmoi.toml` and not asked again. See [Configuration](chezmoi/config.md).
|
||||
2. **Packages**: `run_onchange_before_10-packages` installs everything in `.chezmoidata/packages.yaml` with pacman and yay before any file is written. See [Scripts](chezmoi/scripts.md).
|
||||
3. **Files and externals**: Configs are linked into `$HOME` (symlink mode). The neovim config, fish, and the other externals are cloned or pulled. See [Externals and ignore rules](chezmoi/externals-and-ignore.md).
|
||||
4. **Post-apply scripts**: rustup and cargo crates, the global mise tools (`ghq`, `bun`, `node`, `uv`, `opencode`, `crush`, omp), strata on Linux desktops, then gh extensions (from a fish session), skills, the bat cache, the font cache, yazi plugins, and the systemd user services. tpm arrives earlier as an external.
|
||||
|
||||
> [!NOTE]
|
||||
> The gh extension script skips with a warning until `gh auth login` has run. A skipped run still exits 0, so a plain `chezmoi apply` does not retry it. See [Scripts](chezmoi/scripts.md#20-gh-extensions) for how to re-run it.
|
||||
|
||||
## Updating
|
||||
|
||||
```sh
|
||||
chezmoi update # git pull + apply
|
||||
chezmoi diff # what apply would change
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
# Shell
|
||||
|
||||
fish, bash, and zsh with shared aliases, the starship prompt, atuin history, and the ~/.local/bin scripts.
|
||||
|
||||
fish is the main interactive shell. bash and zsh are set up to feel the same: they share aliases, the prompt, atuin history, and ports of the cd-style helper functions. General-purpose tools are POSIX or bash scripts in `~/.local/bin`, so each has a single implementation that all three shells call.
|
||||
|
||||
| Page | Covers |
|
||||
| --- | --- |
|
||||
| [fish](fish.md) | `~/.config/fish`: `config.fish`, every `conf.d/` file (PATH, env, aliases, ssh-agent, tool init, secrets by name), vi-mode key bindings, the cyberdream theme, completions, the nvm.fish plugin, and every function in `functions/` |
|
||||
| [bash and zsh](bash-zsh.md) | `~/.bashrc`, `~/.zshrc`, zsh and bash key bindings, `~/.config/shell/aliases.sh`, per-shell function ports |
|
||||
| [Aliases](aliases.md) | Every alias from `.chezmoidata/aliases.yaml` plus the fish-only and shell-specific ones |
|
||||
| [Scripts](scripts.md) | Every general-purpose executable in `~/.local/bin` (`pj`, `ghn`, `gop`, `mtime-sort`, `atuin-pick`, …) |
|
||||
| [Starship](starship.md) | `~/.config/starship.toml`: format, modules, symbols, cyberdream palette |
|
||||
| [atuin](atuin.md) | `~/.config/atuin`: history search settings, AI inline integration, cyberdream theme |
|
||||
|
||||
## Shared across shells
|
||||
|
||||
| Concern | fish | bash | zsh |
|
||||
| --- | --- | --- | --- |
|
||||
| Aliases | `conf.d/20-aliases.fish` (hand-maintained) | `~/.config/shell/aliases.sh` (rendered from YAML) | same as bash |
|
||||
| Prompt | starship + transient prompt | starship | starship + transient prompt |
|
||||
| History | atuin (hishtory also sourced if installed) | atuin | atuin, plus suvadu if installed |
|
||||
| Key bindings | vi mode plus custom binds | atuin defaults only | emacs keymap plus ports of the fish binds |
|
||||
| cd-style helpers | `functions/*.fish` | `~/.config/bash/functions/*.bash` | `~/.config/zsh/functions/*.zsh` |
|
||||
| Directory jumping | `cd` wrapper with zoxide fallback, `zoxide init` | `cd` wrapper | `cd` wrapper |
|
||||
|
||||
## Theme
|
||||
|
||||
fish syntax colors, `FZF_DEFAULT_OPTS`, the starship palette, and the atuin theme all use **cyberdream** ([cyberdream.nvim](https://github.com/scottmckendry/cyberdream.nvim)). In fish, `40-tools.fish` also replays quickshell's wallpaper-generated terminal color sequences if that file exists.
|
||||
|
||||
## Role gating
|
||||
|
||||
Everything in this section applies on every role (`desktop`, `laptop`, `server`). The desktop helpers in `~/.local/bin` belong to the [desktop layer](../desktop/README.md) and apply on `desktop`/`laptop` only.
|
||||
|
||||
## Packages
|
||||
|
||||
`.chezmoidata/packages.yaml` supplies the shell tooling on Arch (pacman): `fish`, `starship`, `atuin`, `zoxide`, `fzf`, `fd`, `ripgrep`, `eza`, `bat`, `jq`, `gum`, `glow`, `yazi`, `lazygit`, and `zsh-syntax-highlighting`. The packages script runs `pacman -S --needed`. See [chezmoi scripts](../chezmoi/scripts.md).
|
||||
@@ -0,0 +1,123 @@
|
||||
# Aliases
|
||||
|
||||
Every alias and alias-like helper across fish, bash, and zsh, and where each one is defined.
|
||||
|
||||
## Single source
|
||||
|
||||
`.chezmoidata/aliases.yaml` holds the shell-agnostic aliases as a list of sections, each with `name`/`cmd` items. chezmoi loads the file as template data ([chezmoi data](../chezmoi/data.md)), and `dot_config/shell/aliases.sh.tmpl` ranges over it to produce `~/.config/shell/aliases.sh`, which `~/.bashrc` and `~/.zshrc` source:
|
||||
|
||||
**.chezmoidata/aliases.yaml**
|
||||
|
||||
```yaml
|
||||
aliases:
|
||||
- section: "Editors / file managers"
|
||||
items:
|
||||
- { name: "e", cmd: "nvim" }
|
||||
# …
|
||||
```
|
||||
|
||||
**~/.config/shell/aliases.sh**
|
||||
|
||||
```text
|
||||
# Editors / file managers
|
||||
alias e='nvim'
|
||||
```
|
||||
|
||||
To add an alias for bash and zsh, add it to the YAML and run `chezmoi apply`.
|
||||
|
||||
> [!NOTE]
|
||||
> The YAML and template comments say fish renders the same data through `fish/conf.d/20-aliases.fish.tmpl`. That template no longer exists. `dot_config/fish/conf.d/20-aliases.fish` is a plain, hand-maintained fish file, so a YAML change does not reach fish. The fish column below shows where the two sets differ.
|
||||
|
||||
## Shared aliases
|
||||
|
||||
A ✓ means the alias exists in that shell with the command shown. Where fish uses a different command, the fish command is shown instead.
|
||||
|
||||
### Editors / file managers
|
||||
|
||||
| Alias | bash / zsh (YAML) | fish (`20-aliases.fish`) |
|
||||
| --- | --- | --- |
|
||||
| `e` | `nvim` | ✓ |
|
||||
| `e.d` | `cd ~/.config/dotstate/storage/Rogz/.config && yazi` | not defined |
|
||||
| `e.f` | `cd ~/.config/fish && yazi` | `cd ~/.local/share/chezmoi/dot_config/fish && yazi` |
|
||||
| `e.h` | not defined | `cd ~/.local/share/chezmoi/dot_config/hypr && yazi` (this repo has no `hypr` directory) |
|
||||
| `e.k` | `yazi ~/.config/kitty` | ✓ |
|
||||
| `e.n` | not defined | `yazi ~/.local/share/chezmoi/dot_config/niri` (this repo has no `niri` directory) |
|
||||
| `e.v` | `yazi ~/.config/nvim/lua` | ✓ |
|
||||
| `e.y` | `yazi ~/.config/yazi` | ✓ |
|
||||
| `e.t` | `nvim ~/.config/tmux/tmux.conf` | ✓ |
|
||||
| `hx` | `helix` | ✓ (listed under Tools in fish) |
|
||||
|
||||
The fish `e.f`, `e.h`, `e.k`, and `e.n` aliases open the chezmoi source tree directly. In symlink mode, `~/.config/<tool>` files are symlinks into that tree, so both versions edit the same files. yazi and tmux are external checkouts in `~/.config`, so `e.y` and `e.t` open them there.
|
||||
|
||||
### Quick directory jumps
|
||||
|
||||
| Alias | bash / zsh (YAML) | fish |
|
||||
| --- | --- | --- |
|
||||
| `cdc` | `cd ~/.config && yazi` | ✓ |
|
||||
| `cdd` | `cd ~/Developer && yazi` | not defined |
|
||||
| `cdk` | `cd ~/Documents/Knowledge && obsidian` | ✓ |
|
||||
| `cdl` | `cd ~/.local && yazi` | ✓ |
|
||||
| `cds` | `cd ~/.local/share/services && ls` | ✓ |
|
||||
| `cdw` | `cd ~/Downloads && yazi` | ✓ |
|
||||
| `,,` | not defined | `cd ~/.local/share/chezmoi && yazi` |
|
||||
|
||||
### Tools
|
||||
|
||||
| Alias | Command | fish |
|
||||
| --- | --- | --- |
|
||||
| `dd` | `lazydocker` | ✓ |
|
||||
| `gg` | `lazygit` | ✓ |
|
||||
| `ff` | `exec fish` | ✓ |
|
||||
| `npx` | `bunx` | ✓ |
|
||||
| `sm` | `sudo micro` | ✓ |
|
||||
| `sd` | `ssh prad@<lan-host>` (a machine on the local network) | ✓ |
|
||||
| `oc` | `opencode` | ✓ |
|
||||
| `dss` | `dotstate sync` | ✓ |
|
||||
| `fabric` | `fabric-ai` | ✓ |
|
||||
| `bk` | `bead-kanban` | ✓ |
|
||||
| `sua` | `shelly upgrade-all -n` | ✓ |
|
||||
| `lc` | `leetcode` | ✓ |
|
||||
| `tk` | `task` | ✓ |
|
||||
| `..` | not defined | `cd ..` |
|
||||
|
||||
`dd` shadows coreutils `dd` in interactive shells. Use `command dd` for the real one.
|
||||
|
||||
## fish-only aliases
|
||||
|
||||
### Bun
|
||||
|
||||
| Alias | Command |
|
||||
| --- | --- |
|
||||
| `br` | `bun run` |
|
||||
| `br.d` | `bun run dev` |
|
||||
| `br.b` | `bun run build` |
|
||||
| `br.t` | `bun run test` |
|
||||
| `bi` | `bun install` |
|
||||
| `bg` | `bun install -g` |
|
||||
| `bx` | `bunx` |
|
||||
|
||||
`bg` replaces fish's job-control builtin. Use `builtin bg` to resume a background job.
|
||||
|
||||
## Shell-specific extras
|
||||
|
||||
All three shells define these outside the YAML: in `20-aliases.fish` for fish, and in the tail of `aliases.sh.tmpl` for bash and zsh.
|
||||
|
||||
| Name | Kind | Definition |
|
||||
| --- | --- | --- |
|
||||
| `cpy` | alias | `wl-copy` if available |
|
||||
| `pst` | alias | `wl-paste` if available, else `pbpaste` |
|
||||
| `q` | function | `exit`. Runs `ya emit quit` first when `$YAZI_ID` is set, so the parent yazi also quits |
|
||||
| `celar`, `claer` | alias | `clear` (interactive only) |
|
||||
| `ls` | alias | `eza --icons` when eza is installed (interactive only) |
|
||||
|
||||
## Abbreviations
|
||||
|
||||
No fish `abbr` definitions are committed. Abbreviations live in `fish_variables`, which chezmoi ignores.
|
||||
|
||||
## Related helpers
|
||||
|
||||
The following are functions or scripts, not aliases, and are documented elsewhere:
|
||||
|
||||
- `c`, `cd`, `y`/`yy`, `clip-last`, `ghg`, `ghn`, `gho`: per-shell functions ([fish](fish.md#functions), [bash and zsh](bash-zsh.md#per-shell-functions)).
|
||||
- `pnpmf`, `pnpmfd`, `pnpmfr`, `pnpmfw`, `pnpmfx`: fish workspace helpers ([fish](fish.md#pnpm-filterfish)).
|
||||
- `ai`, `pj`, `gop`, `fab`, `gdc`, and the other `~/.local/bin` tools: [Scripts](scripts.md).
|
||||
@@ -0,0 +1,128 @@
|
||||
# atuin
|
||||
|
||||
Shell history search with a daemon, directory-scoped up-arrow, tmux popups, a CherryBlossom theme, and the atuin AI inline command generator.
|
||||
|
||||
[atuin](https://atuin.sh) replaces shell history search in fish, bash, and zsh. It also provides `atuin ai inline`, a natural-language command generator that the fish config binds to keys.
|
||||
|
||||
| Source (repo) | Target | Notes |
|
||||
| --- | --- | --- |
|
||||
| `dot_config/atuin/private_config.toml` | `~/.config/atuin/config.toml` | `private_`: written as a regular `0600` file, not a symlink |
|
||||
| `dot_config/atuin/themes/cherry-blossom.toml`, `dot_config/atuin/themes/cyberdream.toml` | `~/.config/atuin/themes/*.toml` | symlinks |
|
||||
|
||||
chezmoi runs in symlink mode, but `private_` files are written as real files, so edits to `~/.config/atuin/config.toml` do not reach the repo on their own. Copy them back with `chezmoi re-add` (or `czs`, see [fish](fish.md#functions)). None of the files are templates, and they apply on every role.
|
||||
|
||||
## Installation
|
||||
|
||||
`atuin` is listed in `.chezmoidata/packages.yaml` for pacman, and the shells use that package's binary (`/usr/bin/atuin` on Arch). Each shell runs `atuin init` only if `atuin` is on `PATH`.
|
||||
|
||||
> [!NOTE]
|
||||
> **No standalone install**
|
||||
>
|
||||
> An earlier setup also kept atuin's standalone installer layout in `~/.atuin/bin` (with a cargo-dist `atuin-receipt.json` in the repo) and prepended it to `PATH` from `conf.d/atuin.env.fish` and `. "$HOME/.atuin/bin/env"` in `.bashrc`/`.zshrc`. That copy fell behind the package: the daemon, from the newer package, migrated the history database, and every command the shells ran through the old binary failed with `migration 20260818000000 was previously applied but is missing in the resolved migrations`. The standalone install, its receipt, and the `PATH` lines are gone. Do not reinstall atuin with its curl installer on this setup.
|
||||
|
||||
## Shell integration
|
||||
|
||||
| Shell | Where | What |
|
||||
| --- | --- | --- |
|
||||
| fish | `config.fish`, interactive only | `atuin init fish \| source`, and a copy of the `?` binding (atuin 18.21 removed the `atuin ai init` command that used to emit it) |
|
||||
| fish | `functions/fish_user_key_bindings.fish` | <kbd>ctrl-w</kbd> runs `atuin ai inline` |
|
||||
| bash | end of `~/.bashrc` | `eval "$(atuin init bash)"` |
|
||||
| zsh | end of `~/.zshrc` | `eval "$(atuin init zsh)"` |
|
||||
|
||||
In fish, `40-tools.fish` sources hishtory before atuin, but atuin's init runs later in `config.fish` and takes <kbd>ctrl-r</kbd>. zsh also loads `suv init zsh` (suvadu) if installed.
|
||||
|
||||
### Key bindings
|
||||
|
||||
| Key | Shells | Action |
|
||||
| --- | --- | --- |
|
||||
| <kbd>ctrl-r</kbd> | all | Interactive search (`_atuin_search`) |
|
||||
| <kbd>up</kbd> | all | Search opened from the up arrow. `filter_mode_shell_up_key_binding = "directory"` limits it to history from the current directory |
|
||||
| <kbd>?</kbd> | fish, vi normal mode | On an empty buffer, runs `atuin ai inline --hook` |
|
||||
| <kbd>ctrl-w</kbd> | fish, insert and normal mode | `atuin ai inline`, then repaint |
|
||||
| <kbd>ctrl+shift+p</kbd> | kitty | [`atuin-pick`](scripts.md#atuin-pick): directory-filtered search in a split, running the pick in a fresh fish |
|
||||
|
||||
Under tmux, atuin's `[tmux]` settings open the search in a tmux popup (80% wide, 60% tall) instead of inline.
|
||||
|
||||
## AI inline
|
||||
|
||||
Commit `a82921c` ("add Atuin AI inline integration and keybinding") added the <kbd>ctrl-w</kbd> bind to `fish_user_key_bindings.fish`, `atuin ai init fish | source` to `config.fish`, and a copy of the `_atuin_ai_question_mark` function that command printed, with `bind "?" _atuin_ai_question_mark`. atuin 18.21 removed `atuin ai init`, so `config.fish` keeps only the copied function and binding; `atuin ai inline --hook` still works.
|
||||
|
||||
The config also sets `[ai] enabled = true`.
|
||||
|
||||
`_atuin_ai_question_mark` works like this:
|
||||
|
||||
- **Buffer empty (or just `?`):** clears the line, prints OSC `133;C` to `/dev/tty` to close the terminal's semantic prompt zone (so shell-integrated terminals do not erase the TUI during a prompt reflow), then runs `atuin ai inline --hook` with stdout and stderr swapped. The TUI draws on the terminal, and the result comes back tagged:
|
||||
|
||||
| Output prefix | Effect |
|
||||
| --- | --- |
|
||||
| `__atuin_ai_print__:` | Print the text, repaint |
|
||||
| `__atuin_ai_cancel__` | Repaint only |
|
||||
| `__atuin_ai_execute__:` | Put the command on the line and execute it |
|
||||
| `__atuin_ai_insert__:` | Put the command on the line for editing |
|
||||
| anything else, non-empty | Put it on the line for editing |
|
||||
|
||||
- **Buffer not empty:** inserts a literal `?` under emacs bindings. Under vi or hybrid bindings, which this config uses, it does nothing.
|
||||
|
||||
`bind "?"` in `config.fish` has no `-M` flag, so it applies to fish's `default` mode, which is vi **normal** mode here. In insert mode <kbd>?</kbd> types a question mark. To ask for a command, press <kbd>Esc</kbd> and then <kbd>?</kbd> on an empty line, or press <kbd>ctrl-w</kbd> from either mode. The <kbd>ctrl-w</kbd> bind calls `atuin ai inline` without `--hook` and replaces fish's default <kbd>ctrl-w</kbd> word deletion.
|
||||
|
||||
bash and zsh have no AI binding.
|
||||
|
||||
## Configuration
|
||||
|
||||
`config.toml` is atuin's generated sample with a few keys set. The rest are commented-out defaults. The settings that take effect:
|
||||
|
||||
| Key | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `filter_mode_shell_up_key_binding` | `"directory"` | Up-arrow search shows only the current directory's history |
|
||||
| `dotfiles.enabled` | `true` | Enables atuin's dotfiles feature (aliases and vars managed by atuin) |
|
||||
| `daemon.enabled` | `true` | Uses the background daemon |
|
||||
| `daemon.autostart` | `true` | atuin starts and manages the daemon itself |
|
||||
| `pty_proxy.enabled` | `false` | `atuin init` does not re-exec the shell inside `atuin pty-proxy` |
|
||||
| `theme.name` | `"cherry-blossom"` | Loads `themes/cherry-blossom.toml` |
|
||||
| `tmux.enabled` | `true` | Search opens in a tmux popup when inside tmux (tmux 3.2+) |
|
||||
| `tmux.width` / `tmux.height` | `"80%"` / `"60%"` | Popup size |
|
||||
| `ai.enabled` | `true` | Enables `atuin ai` |
|
||||
|
||||
No sync server, key, or session is configured in the repo. atuin keeps its encryption key and session under `~/.local/share/atuin`, outside chezmoi, and uses the default sync server.
|
||||
|
||||
> [!WARNING]
|
||||
> **Misplaced keys**
|
||||
>
|
||||
> Several keys sit after the `[dotfiles]` table header, so TOML puts them inside `[dotfiles]` rather than at the top level, and atuin ignores them. `atuin config get --resolved` confirms it:
|
||||
>
|
||||
> | Key as written | Intended | Resolved |
|
||||
> | --- | --- | --- |
|
||||
> | `search_mode` | `"daemon-fuzzy"` | `fuzzy` |
|
||||
> | `enter_accept` | `true` | `false` |
|
||||
> | `workspaces` | `true` | `false` |
|
||||
> | `style` | `"auto"` | `compact` |
|
||||
> | `inline_height_shell_up_key_binding` | `20` | unknown key |
|
||||
> | `auto_sync` | `true` | `true` (the default) |
|
||||
> | `update_check` | `true` | `true` (the default) |
|
||||
>
|
||||
> `syntax_highlight = true` has the same problem: it sits under `[ai]`, while the comment block above it documents the `[ui]` table.
|
||||
>
|
||||
> To apply these settings, move the keys above `[dotfiles]` (and `syntax_highlight` under `[ui]`) in `dot_config/atuin/private_config.toml`.
|
||||
|
||||
## Theme
|
||||
|
||||
`themes/cherry-blossom.toml` defines the selected theme, from the CherryBlossom palette (`palettes/CherryBlossom.json` in [prdlk/noctalia](https://github.com/prdlk/noctalia)) shared with Ghostty, tmux, lazygit, and omp:
|
||||
|
||||
| Key | Color |
|
||||
| --- | --- |
|
||||
| `AlertInfo` | `#a78266` |
|
||||
| `AlertWarn` | `#ae825f` |
|
||||
| `AlertError` | `#bf615d` |
|
||||
| `Annotation` | `#a6958f` |
|
||||
| `Guidance` | `#8f9aa0` |
|
||||
| `Important` | `#cf716f` |
|
||||
| `Title` | `#cf716f` |
|
||||
| `Muted` | `#a6958f` |
|
||||
| `SyntaxCommand` | `#cf716f` |
|
||||
| `SyntaxFlag` | `#a78266` |
|
||||
| `SyntaxString` | `#a6b084` |
|
||||
| `SyntaxVariable` | `#c17e4a` |
|
||||
| `SyntaxOperator` | `#ad6756` |
|
||||
| `SyntaxComment` | `#a6958f` |
|
||||
|
||||
`themes/cyberdream.toml` is still shipped (the palette shared with [fish](fish.md#theme) and [Starship](starship.md#palette)) but not selected.
|
||||
@@ -0,0 +1,156 @@
|
||||
# bash and zsh
|
||||
|
||||
Secondary shells that share fish's aliases, starship prompt, atuin history, and cd-style helper functions.
|
||||
|
||||
bash and zsh are set up so a shell other than fish feels the same: the same aliases, the same starship prompt, atuin history, ports of the fish helpers that have to change the shell's own directory, and the same picker key bindings.
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_bashrc` | `~/.bashrc` |
|
||||
| `dot_zshrc` | `~/.zshrc` |
|
||||
| `dot_config/zsh/keybinds.zsh` | `~/.config/zsh/keybinds.zsh` |
|
||||
| `dot_config/bash/keybinds.bash` | `~/.config/bash/keybinds.bash` |
|
||||
| `dot_config/shell/aliases.sh.tmpl` | `~/.config/shell/aliases.sh` (template) |
|
||||
| `dot_config/bash/functions/*.bash` | `~/.config/bash/functions/` |
|
||||
| `dot_config/zsh/functions/*.zsh` | `~/.config/zsh/functions/` |
|
||||
|
||||
`aliases.sh.tmpl` is the only template. It renders the alias list from `.chezmoidata/aliases.yaml` ([chezmoi data](../chezmoi/data.md)). The other files apply as-is on every role.
|
||||
|
||||
## Installation
|
||||
|
||||
bash is part of the base system. zsh itself is not listed in `.chezmoidata/packages.yaml`, but `zsh-syntax-highlighting`, `starship`, `atuin`, `eza`, and `zoxide` are. `suvadu` (the `suv` history tool) is declared under `packages.shared.cargo` and installed by [15-tools](../chezmoi/scripts.md#15-tools).
|
||||
|
||||
## ~/.bashrc
|
||||
|
||||
Interactive shells only (`[[ $- != *i* ]] && return`). In order:
|
||||
|
||||
1. **PATH.** Prepends `~/.local/bin` if missing, sources `~/.cargo/env` if present, and appends `~/.maestro/bin` if it exists. Exports `GHQ_ROOT` and `CODE_ROOT` as `~/Code`.
|
||||
2. **Go.** `GOPATH=~/.local/share/go`, `GOBIN=$GOPATH/bin`, and `GOBIN` prepended to `PATH`. mise provides `GOROOT`. The same two values are also in `~/.config/go/env` (`dot_config/go/env.tmpl`), Go's own config file, so every `go` invocation uses them even without these variables (cron, `sudo -u`, `env -i`, GUI apps).
|
||||
3. **Android SDK.** `ANDROID_HOME` and `ANDROID_SDK_ROOT` set to `~/.local/share/android`. `platform-tools`, `cmdline-tools/latest/bin`, and `emulator` are appended if they exist. Exports `GITHUB_TOKEN` from `gh auth token` unless it is already set.
|
||||
4. **Aliases.** Sources `~/.config/shell/aliases.sh`.
|
||||
5. **Functions.** Sources every `~/.config/bash/functions/*.bash`.
|
||||
6. **Flyline.** If `~/.local/lib/libflyline.so` exists, loads it with `enable -f … flyline` for richer line editing.
|
||||
7. Sources `~/.config/envman/load.sh` if it is non-empty.
|
||||
8. **Prompt.** `eval "$(starship init bash)"` if starship is installed.
|
||||
9. **television.** `eval "$(tv init bash)"` if `tv` is installed (the `_tv` completion), then sources `~/.config/bash/keybinds.bash`.
|
||||
10. **atuin.** `eval "$(atuin init bash)"` if atuin is installed (the package's binary; see [atuin](atuin.md#installation)). atuin comes after tv, so it keeps <kbd>ctrl-r</kbd> and <kbd>up</kbd>.
|
||||
|
||||
## ~/.zshrc
|
||||
|
||||
1. **History.** `HISTFILE=~/.zsh_history`, `HISTSIZE=10000`, `SAVEHIST=10000`, `setopt SHARE_HISTORY HIST_IGNORE_DUPS`.
|
||||
2. **Completion.** `autoload -Uz compinit && compinit`.
|
||||
3. The same PATH, Go, Android, and `GITHUB_TOKEN` blocks as `.bashrc`, including `GHQ_ROOT` and `CODE_ROOT`.
|
||||
4. Sources `~/.config/shell/aliases.sh`.
|
||||
5. Sources every `~/.config/zsh/functions/*.zsh`.
|
||||
6. Sources `~/.config/zsh/keybinds.zsh`.
|
||||
7. `eval "$(suv init zsh)"` if suvadu is installed.
|
||||
8. Sources `~/.config/envman/load.sh` if it is non-empty.
|
||||
9. **Prompt.** If starship is installed: `eval "$(starship init zsh)"`, `ZLE_RPROMPT_INDENT=0` (the right-prompt clock sits flush with the edge), and a transient prompt. A `line-finish` zle hook swaps `PROMPT`/`RPROMPT` to the `transient` and `transient_right` starship profiles (`❯`, colored by the last exit status, and the time) and redraws, so past prompts collapse as in fish; a `precmd` hook restores the full prompt. Starship's zsh init also sets `PROMPT2`, the `∙` continuation prompt for unfinished lines. See [Starship](starship.md#shell-integration).
|
||||
**Window title.** A `precmd` hook ports `fish_title`: it sets the terminal title to `%~`, prefixed with the first 10 characters of the hostname over SSH, and inside tmux also sets the window name (`ESC k`), like fish's `50-terminal-title.fish`. Unlike fish's `prompt_pwd`, the path is not abbreviated.
|
||||
10. `eval "$(tv init zsh)"` if `tv` is installed (<kbd>ctrl-t</kbd> smart autocomplete).
|
||||
11. **Syntax highlighting.** Sources the first `zsh-syntax-highlighting.zsh` found in `/usr/share/zsh/plugins/zsh-syntax-highlighting/`, or `/usr/local/share/zsh-syntax-highlighting/`. The comment says this must stay last, because it wraps every zle widget defined before it.
|
||||
12. **atuin.** `eval "$(atuin init zsh)"` if atuin is installed. This line comes after the syntax-highlighting block, so despite the comment, syntax highlighting is not actually last. atuin loads after tv, so it keeps <kbd>ctrl-r</kbd>.
|
||||
|
||||
## zsh key bindings
|
||||
|
||||
`keybinds.zsh` ports `fish_user_key_bindings.fish` to zle. Unlike fish, zsh uses the **emacs** keymap: `bindkey -e` pins it so that `$EDITOR=nvim` does not switch zsh into vi mode. `unsetopt flow_control` frees <kbd>ctrl-q</kbd> and <kbd>ctrl-s</kbd> from the terminal.
|
||||
|
||||
A generic widget `_zle-run` is registered as `run-<cmd>` for `purple`, `clip-last`, `omp`, `yy`, and `lazygit`. It clears the pending display (`zle -I`), runs the command with the tty on stdin, then `zle reset-prompt`. Widgets run in the shell process, so directory changes made by `gho` and `yy` persist.
|
||||
|
||||
The tv pickers have their own widgets, because `tv --inline` opens on the line below the prompt and leaves the cursor there on exit. `zle -I` followed by `zle reset-prompt` redraws the prompt where zle last drew it, wiping anything printed below; `zle -I` alone draws a fresh prompt under the output.
|
||||
|
||||
| Widget | Runs | Then |
|
||||
| --- | --- | --- |
|
||||
| `tv-git-files` | `tv git-files --inline` | `\033[A`, `reset-prompt` |
|
||||
| `gho-inline` | `gho` | cwd unchanged: `\033[A`, `reset-prompt`. After a cd: nothing, so the new prompt lands below onefetch |
|
||||
| `pj-inline`, `pj-down` | `_zle-pj`: `pj` with `PJ_STATE` set to a temp file (`PJ_QUIET=1` for `pj-down`) | `open` (cancelled): `\033[A`, `reset-prompt`. `ran`: nothing, so the task output stays. Empty (no task sources): `reset-prompt` |
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>ctrl-e</kbd> | `tv-git-files` (pick a file, open it in nvim) |
|
||||
| <kbd>ctrl-f</kbd> | `yy` |
|
||||
| <kbd>ctrl-b</kbd> | `purple` (an external command; this repo does not provide it) |
|
||||
| <kbd>ctrl-y</kbd> | `clip-last` |
|
||||
| <kbd>ctrl-o</kbd> | `gho-inline` (`ghq-repos` cable, cd) |
|
||||
| <kbd>ctrl-p</kbd> | `pj-inline` (`pj` cable) |
|
||||
| <kbd>ctrl-t</kbd> | tv smart autocomplete (from `tv init zsh`) |
|
||||
| <kbd>down</kbd> | `pj-down`: `PJ_QUIET=1 pj` on an empty prompt, otherwise `down-line-or-history` (bound to `^[[B` and `^[OB`) |
|
||||
| <kbd>ctrl-a</kbd> | `omp-session`: resume or start omp for the current repo ([Scripts](scripts.md#omp-session)) |
|
||||
| <kbd>ctrl-n</kbd> | `omp`: always a new omp session (replaces zsh's `down-line-or-history` on that key; <kbd>down</kbd> still walks history) |
|
||||
| <kbd>ctrl-g</kbd> | `lazygit` |
|
||||
| <kbd>ctrl-q</kbd> | `exit` |
|
||||
| <kbd>ctrl-c</kbd> | At the prompt: kill the buffer, clear the screen and kitty scrollback (`ESC[2J ESC[3J`), reset the prompt |
|
||||
|
||||
<kbd>ctrl-c</kbd> is the tty interrupt character, so `bindkey '^C'` would never fire. The binding is a `TRAPINT` function instead. At the prompt it runs `zle .kill-buffer`, prints the clear sequence, and runs `zle .reset-prompt`. While a command is running, SIGINT keeps its normal behavior (returns `128+SIGINT`).
|
||||
|
||||
The fish-only binds <kbd>ctrl-w</kbd> (atuin AI) and <kbd>?</kbd> have no zsh equivalent. <kbd>ctrl-h/j/k/l</kbd> belong to kitty ([kitty](../terminal/kitty.md)) and are not bound here. The cables are described on the [television](../tools/television.md) page.
|
||||
|
||||
## bash key bindings
|
||||
|
||||
`keybinds.bash` gives bash the same picker keys as fish and zsh. bash keeps readline's default emacs keymap. Every picker runs `tv --inline`; when nothing was printed below the prompt, `_kb_up` (`\033[A` on `/dev/tty`) steps back so readline redraws the prompt in place after `bind -x`.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>ctrl-o</kbd> | Macro `\C-x1\C-x2\C-x3\C-x0` (below): pick from the `ghq-repos` cable and cd |
|
||||
| <kbd>ctrl-e</kbd> | `_kb_git_files`: `tv git-files --inline`, then `_kb_up` |
|
||||
| <kbd>ctrl-p</kbd> | `_kb_pj`: `pj` with `PJ_STATE` set to a temp file; `_kb_up` only when the state is `open` (cancelled) |
|
||||
| <kbd>down</kbd> | `PJ_QUIET=1 _kb_pj` on an empty prompt, otherwise `history-search-forward` |
|
||||
| <kbd>ctrl-t</kbd> | `_kb_tv_autocomplete`: runs `tv --autocomplete-prompt … --inline` on the text left of the cursor, `_kb_up`, and inserts the picks there, each `printf %q`-quoted |
|
||||
|
||||
`bind -x` runs in the shell process, so a `cd` persists, but readline keeps drawing the prompt it started with. <kbd>ctrl-o</kbd> is therefore a macro:
|
||||
|
||||
1. `\C-x1` runs `_kb_gho`: stashes `READLINE_LINE`, runs `tv ghq-repos --inline`, `_kb_up`, and cds into the pick. It does not call the `gho` function. After a cd it empties the line and rebinds `\C-x2` to `accept-line` and `\C-x3` to `bind -x _kb_gho_show`; when cancelled it rebinds both to `redraw-current-line`.
|
||||
2. `\C-x2` accepts the empty line, so bash prints a fresh prompt for the new cwd. Nothing reaches history.
|
||||
3. `\C-x3` runs `_kb_gho_show` (`clear`, then `onefetch` in git repos); readline redraws the prompt below the output.
|
||||
4. `\C-x0` runs `_kb_restore`, which puts the typed text back.
|
||||
|
||||
<kbd>down</kbd> (`\e[B` and `\eOB`) is the macro `\C-x4\C-x5`. `bind -x` cannot call readline functions, so `_kb_down` (`\C-x4`) rebinds `\C-x5` to `history-search-forward` when the line is non-empty, or runs `PJ_QUIET=1 _kb_pj` and rebinds `\C-x5` to `redraw-current-line`.
|
||||
|
||||
## Shared aliases: aliases.sh
|
||||
|
||||
`~/.config/shell/aliases.sh` is rendered from `.chezmoidata/aliases.yaml`. Each YAML `section` becomes a comment, and each item becomes `alias <name>='<cmd>'`. The template then appends shell-specific extras that are not in the YAML:
|
||||
|
||||
**~/.config/shell/aliases.sh**
|
||||
|
||||
```sh
|
||||
# Clipboard
|
||||
if command -v wl-copy >/dev/null 2>&1; then
|
||||
alias cpy='wl-copy'
|
||||
alias pst='wl-paste'
|
||||
fi
|
||||
|
||||
q() {
|
||||
if [ -n "$YAZI_ID" ] && command -v ya >/dev/null 2>&1; then
|
||||
ya emit quit 2>/dev/null
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
alias celar='clear'
|
||||
alias claer='clear'
|
||||
if command -v eza >/dev/null 2>&1; then
|
||||
alias ls='eza --icons'
|
||||
fi
|
||||
```
|
||||
|
||||
- **Clipboard detection.** `cpy`/`pst` map to Wayland's `wl-copy`/`wl-paste` when available.
|
||||
- **`q`.** Exits the shell. If the shell was spawned from yazi's own `q` binding (`$YAZI_ID` set), it runs `ya emit quit` first so the parent yazi closes too instead of bouncing back.
|
||||
- **Typo guards and eza.** `celar`/`claer` → `clear`, and `ls` → `eza --icons` when eza is installed. The file is sourced only by interactive shells, so scripts keep the real `ls`.
|
||||
|
||||
The full alias table, including the differences from fish, is on the [Aliases](aliases.md) page.
|
||||
|
||||
## Per-shell functions
|
||||
|
||||
Each directory holds one function per file. bash and zsh source every file at startup; the bash and zsh versions have identical bodies.
|
||||
|
||||
| Function | Behavior |
|
||||
| --- | --- |
|
||||
| `c [dir]` | `clear` with no argument, `cd "$1"` with one |
|
||||
| `cd [dir]` | No argument: `$HOME`. `-`: previous dir. Otherwise `builtin cd`, falling back to `zoxide query -- "$1"`. Records the result with `zoxide add "$PWD"` and preserves cd's exit status |
|
||||
| `clip-last` | Re-runs the previous history entry (`fc -ln -1 -1`, skipping itself if the line editor already recorded `clip-last`), pipes the output to `wl-copy`, and sends a `notify-send -a shell` notification if available |
|
||||
| `ghg <repo>` | `ghg-clone "$@"`, cd into the printed path, `clear && onefetch` |
|
||||
| `ghn <name>` | `command ghn "$@"`, cd into the printed path |
|
||||
| `gho` | `dir=$(tv ghq-repos --inline)`, return if empty, cd into it, `clear`, and `onefetch` if it is a git repo. No cursor handling; bash <kbd>ctrl-o</kbd> does not call it |
|
||||
| `yy [args]` | `yazi --cwd-file=<tmp>`, then cd to the directory written there if it changed |
|
||||
|
||||
The `ghg` helper script is named `ghg-clone` because an unrelated cargo binary already uses the name `ghg`. The [Scripts](scripts.md#why-some-helpers-are-shell-functions) page explains why these helpers are shell functions and not scripts.
|
||||
@@ -0,0 +1,318 @@
|
||||
# fish
|
||||
|
||||
The primary interactive shell — conf.d layout, environment, vi-mode key bindings, the cyberdream theme, completions, and every autoloaded function.
|
||||
|
||||
fish is the primary interactive shell. The config is split into autoloaded files: `conf.d/` for startup snippets, `functions/` for one function per file, `completions/` for completion scripts, and `themes/` for the color theme. `config.fish` runs last and holds the prompt and atuin setup.
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_config/fish/config.fish` | `~/.config/fish/config.fish` |
|
||||
| `dot_config/fish/conf.d/*.fish` | `~/.config/fish/conf.d/` |
|
||||
| `dot_config/fish/functions/*.fish` | `~/.config/fish/functions/` |
|
||||
| `dot_config/fish/completions/*.fish` | `~/.config/fish/completions/` |
|
||||
| `dot_config/fish/themes/cyberdream.theme` | `~/.config/fish/themes/cyberdream.theme` |
|
||||
| `dot_config/fish/fish_plugins` | `~/.config/fish/fish_plugins` |
|
||||
| `dot_config/fish/auto-Niri.fish` | not applied: it is listed in `.chezmoiignore.tmpl` (see below) |
|
||||
|
||||
`dot_config/fish` is a git submodule ([prdlk/fish](https://github.com/prdlk/fish)). Edit inside the submodule, commit there, then commit the updated pointer in the chezmoi repo. None of these files are templates. chezmoi runs in symlink mode, so each target is a symlink back into the source tree and edits to `~/.config/fish/...` land in the submodule directly. `~/.config/fish/fish_variables` (universal variables, including the selected theme) is listed in `.chezmoiignore.tmpl` and stays local to each machine. `~/.config/fish/conf.d/secrets.fish` is local-only: it is listed in `.chezmoiignore.tmpl` and in the fish repo's `.gitignore`, and never committed.
|
||||
|
||||
## Installation
|
||||
|
||||
`fish` is in `.chezmoidata/packages.yaml` under `packages.arch.pacman`, and `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl` installs it with pacman. The tools that fish initializes (starship, zoxide, atuin, fzf, eza, gum, yazi, television, mise) come from the same manifest; Bun is a global mise tool (`packages.shared.mise`) and rustup comes from [15-tools](../chezmoi/scripts.md#15-tools). hishtory, envman, and the Go, Android, and Foundry toolchains are not in the manifest; fish initializes each one only if it is present.
|
||||
|
||||
### Plugins
|
||||
|
||||
`fish_plugins` is a [fisher](https://github.com/jorgebucaran/fisher) manifest with one entry:
|
||||
|
||||
**~/.config/fish/fish_plugins**
|
||||
|
||||
```text
|
||||
jorgebucaran/nvm.fish
|
||||
```
|
||||
|
||||
The plugin's files are committed to the repo (`conf.d/nvm.fish`, `functions/nvm.fish`, `functions/_nvm_*.fish`, `completions/nvm.fish`), so fish has `nvm` without fisher. The plugin stores Node builds in `$XDG_DATA_HOME/nvm` (default `~/.local/share/nvm`) and downloads from `https://nodejs.org/dist`. If `nvm_default_version` is set, it runs `nvm use --silent $nvm_default_version` in each interactive shell.
|
||||
|
||||
## Startup order
|
||||
|
||||
fish sources `conf.d/*.fish` in name order and then `config.fish`. The numbered files load first:
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `00-path.fish` | `PATH` entries and `PNPM_HOME` |
|
||||
| `10-env.fish` | Non-secret environment, greeting, fzf colors |
|
||||
| `20-aliases.fish` | Aliases, clipboard aliases, `q`, typo guards |
|
||||
| `30-ssh-agent.fish` | One ssh-agent shared across shells |
|
||||
| `40-tools.fish` | mise, television, starship, zoxide, entire, hishtory, unsloth |
|
||||
| `50-terminal-title.fish` | tmux/screen window name from `$PWD` |
|
||||
| `inir-env.fish` | `INIR_VENV` and `ILLOGICAL_IMPULSE_VIRTUAL_ENV` → `~/.local/state/quickshell/.venv` |
|
||||
| `inir-path.fish` | Prepends `~/.local/bin` to `PATH` if missing |
|
||||
| `nvm.fish` | nvm.fish plugin events and default-version activation |
|
||||
| `opencode-background-subagents.fish` | `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, `OPENCODE_ENABLE_EXA=1` |
|
||||
| `pnpm-filter.fish` | `pnpmf*` workspace helpers |
|
||||
| `rustup.fish` | Sources `~/.cargo/env.fish` |
|
||||
| `secrets.fish` | API credentials (names below) |
|
||||
| `uv.env.fish` | Sources `~/.local/bin/env.fish` (written by the uv installer) |
|
||||
|
||||
### 00-path.fish
|
||||
|
||||
Runs for every shell, including non-interactive ones, so scripts get the same `PATH`.
|
||||
|
||||
| Entry | Position |
|
||||
| --- | --- |
|
||||
| `~/.bun/bin`, `~/.local/share/go/bin`, `~/.maestro/bin`, `~/.grok/bin`, `~/.opencode/bin` | prepended |
|
||||
| `~/.foundry/bin` | appended |
|
||||
| `~/.local/share/android/platform-tools`, `cmdline-tools/latest/bin`, `emulator` | appended |
|
||||
| `~/local/share/pnpm` | appended (note the missing dot; this path does not match `PNPM_HOME`) |
|
||||
| `$PNPM_HOME/bin` | prepended; `PNPM_HOME=~/.local/share/pnpm` |
|
||||
|
||||
`~/.local/bin` comes from `inir-path.fish` and `~/.cargo/bin` from `rustup.fish`.
|
||||
|
||||
### 10-env.fish
|
||||
|
||||
| Variable | Value |
|
||||
| --- | --- |
|
||||
| `fish_greeting` | empty (no greeting) |
|
||||
| `FISH_BOTTOM_PROMPT` | `0` unless inherited. `1` pins the prompt to the bottom row |
|
||||
| `HSA_OVERRIDE_GFX_VERSION` | `11.5.0` (ROCm) |
|
||||
| `ROCR_VISIBLE_DEVICES`, `HIP_VISIBLE_DEVICES` | `0` |
|
||||
| `__EGL_VENDOR_LIBRARY_FILENAMES`, `VK_DRIVER_FILES` | Mesa's `50_mesa.json` and RADV's `radeon_icd.json`, only when `/dev/dri/by-path/pci-0000:c4:00.0-render` (the Strix Halo iGPU) exists. Keeps the session off the compute-only eGPU, see [Thunderbolt eGPU](../desktop/README.md#thunderbolt-egpu-compute-only) |
|
||||
| `EGET_BIN` | `~/.local/bin` |
|
||||
| `GHQ_ROOT`, `CODE_ROOT` | `~/Code` |
|
||||
| `ANDROID_HOME`, `ANDROID_SDK_ROOT` | `~/.local/share/android` |
|
||||
| `GITEA_LOGIN` | `prad.codes` |
|
||||
| `GITLAB_HOST` | `git.sonr.org` |
|
||||
| `GITHUB_TOKEN` | `gh auth token`, unless already set and only if `gh` is logged in |
|
||||
| `EDITOR`, `VISUAL` | `nvim` |
|
||||
| `BROWSER` | `/usr/bin/brave-origin` if that binary exists |
|
||||
| `CHROME_EXECUTABLE` | `/usr/bin/chromium` if it exists |
|
||||
| `PUPPETEER_EXECUTABLE_PATH` | `~/.local/bin/chrome-headless-shell` |
|
||||
| `ZEAL_DOCSETS` | the Zeal flatpak docset directory |
|
||||
| `SHARP_IGNORE_GLOBAL_LIBVIPS` | `1` |
|
||||
| `FZF_DEFAULT_OPTS` | cyberdream fzf theme (below) |
|
||||
|
||||
`GOPATH`/`GOBIN` are not set here. The comment explains why: mise's fish hook re-applies its `[env]` on every prompt, so Go paths are pinned in `~/.config/mise/config.toml`, and mise provides `GOROOT`. `~/.config/go/env` holds the same values for any `go` run outside a shell.
|
||||
|
||||
**~/.config/fish/conf.d/10-env.fish**
|
||||
|
||||
```fish
|
||||
set -gx FZF_DEFAULT_OPTS "
|
||||
--height=~40%
|
||||
--layout=default
|
||||
--info=inline-right
|
||||
--border=rounded
|
||||
--prompt=' '
|
||||
--pointer='▶'
|
||||
--marker='✓'
|
||||
--color=fg:#ffffff,fg+:#ffffff,bg:-1,bg+:#3c4048
|
||||
--color=hl:#5ef1ff,hl+:#5ef1ff
|
||||
--color=info:#7b8496,header:#7b8496,border:#7b8496
|
||||
--color=prompt:#5ef1ff,pointer:#ffaecf,marker:#5eff6c,spinner:#f1ff5e
|
||||
--color=gutter:-1,query:#ffffff
|
||||
"
|
||||
```
|
||||
|
||||
### 20-aliases.fish
|
||||
|
||||
Editor, Bun, directory-jump, and tool aliases, followed by:
|
||||
|
||||
- `cpy`/`pst`: `wl-copy`/`wl-paste` if `wl-copy` exists (the shared fish config also has a `pbcopy`/`pbpaste` fallback that never triggers here).
|
||||
- `q`: exits the shell. If `$YAZI_ID` is set and `ya` exists, it first runs `ya emit quit` so the yazi that spawned the shell quits too.
|
||||
- Interactive only: `celar` and `claer` → `clear`, and `ls` → `eza --icons` when eza is installed. `clear` itself is not aliased, because an alias would shadow `functions/clear.fish`.
|
||||
|
||||
The full list is on the [Aliases](aliases.md) page. This file is plain fish, not rendered from `.chezmoidata/aliases.yaml`, so its alias set differs from bash/zsh in a few places.
|
||||
|
||||
### 30-ssh-agent.fish
|
||||
|
||||
Interactive shells without `SSH_AUTH_SOCK` source `~/.ssh/agent.env.fish` and probe with `ssh-add -l`. Exit code 2 means no agent is reachable. In that case it starts `ssh-agent -c`, rewrites its csh `setenv` lines into `set -gx`, saves them to `~/.ssh/agent.env.fish`, and sources the file. An `SSH_AUTH_SOCK` provided from outside (systemd unit, forwarding, keyring) is left alone.
|
||||
|
||||
### 40-tools.fish
|
||||
|
||||
- `mise activate fish` runs for every shell, so scripts see mise shims too.
|
||||
- Interactive only, in order:
|
||||
1. Print `~/.local/state/quickshell/user/generated/terminal/sequences.txt` if present. These are escape sequences that apply wallpaper-generated Material You terminal colors from quickshell.
|
||||
2. `tv init fish` (television), if installed.
|
||||
3. `starship init fish`, `zoxide init fish`, and `entire completion fish`, each only if installed.
|
||||
4. `tv init fish` a second time. The comment says it runs before hishtory so hishtory keeps <kbd>ctrl-r</kbd>.
|
||||
5. Source `~/.hishtory/config.fish` if present.
|
||||
6. Activate the unsloth studio venv (`~/.unsloth/studio/unsloth_studio/bin/activate.fish`) if present.
|
||||
|
||||
atuin is initialized later, in `config.fish`, and binds <kbd>ctrl-r</kbd> itself.
|
||||
|
||||
### 50-terminal-title.fish
|
||||
|
||||
Defines `terminal_title`, which runs on every `PWD` change and prints `ESC k <prompt_pwd> ESC \`. That sequence sets the tmux or screen window name. `functions/fish_title.fish` sets the regular terminal title.
|
||||
|
||||
### pnpm-filter.fish
|
||||
|
||||
pnpm workspace helpers. `__pnpm_pick_pkg` lists workspace packages with `pnpm m ls --depth -1 --json | jq`, and you pick one with `gum filter`.
|
||||
|
||||
| Function | Does |
|
||||
| --- | --- |
|
||||
| `pnpmf <deps…>` | Pick a package, then `pnpm --filter <pkg> add <deps>` inside a `gum spin` |
|
||||
| `pnpmfd <deps…>` | Same with `add -D` |
|
||||
| `pnpmfr <deps…>` | `pnpm --filter <pkg> remove <deps>` |
|
||||
| `pnpmfw` | Pick a target package and a sibling, then `pnpm --filter <pkg> add <sibling> --workspace` |
|
||||
| `pnpmfx` | Pick a package, then pick one of its `package.json` scripts with `gum filter` and run it |
|
||||
|
||||
### secrets.fish
|
||||
|
||||
Exports credentials as global variables. The values are machine-specific and are not documented here. Variable names:
|
||||
|
||||
`PGDATABASE` (local Postgres connection string), `GITLAB_TOKEN`, `OPPER_API_KEY`, `OPENAI_API_KEY`, `GROQ_API_KEY`, `WEBAWESOME_NPM_TOKEN`, `TAVILY_API_KEY`, `FINANCIAL_DATASETS_API_KEY`, `PIMLICO_API_KEY`, `WAKATIME_API_KEY`, `LINEAR_CLI_PROFILE`, `LINEAR_API_KEY`, `LINEAR_TEAM_ID`.
|
||||
|
||||
> [!WARNING]
|
||||
> Put your own values in `~/.config/fish/conf.d/secrets.fish` on each machine. Do not reuse credentials from another clone.
|
||||
|
||||
## config.fish
|
||||
|
||||
The header comment says the file should stay empty, but it holds the following:
|
||||
|
||||
1. **Starship with transient prompt.** `starship_transient_prompt_func` renders `starship module character` and `starship_transient_rprompt_func` renders `starship module time`. Then `starship init fish | source` and `enable_transience` run, so past prompts collapse to `❯` with the time on the right. See [Starship](starship.md).
|
||||
2. **Bottom-pinned prompt.** In interactive shells with `FISH_BOTTOM_PROMPT=1`, `tput cup $LINES 0` moves the cursor to the last row. The comment calls `1` the default, but `10-env.fish` sets `0`, so the prompt stays at the top unless you export `FISH_BOTTOM_PROMPT=1`.
|
||||
3. `fish_add_path ~/.grok/bin` (grok installer block).
|
||||
4. Sources `~/.config/envman/load.fish` if it is non-empty.
|
||||
5. **atuin.** Interactive shells run `atuin init fish | source`. See [atuin](atuin.md).
|
||||
6. `fish_add_path ~/.cmux/bin` and `~/.cache/.bun/bin`.
|
||||
7. Sources the first readable `~/.config/herdr/plugins/github/herdr-automatic-rename-*/shell/hook.fish`. This is herdr's automatic pane-rename hook ([herdr](../terminal/herdr.md)).
|
||||
8. A copy of the `_atuin_ai_question_mark` function that `atuin ai init fish` used to emit (atuin 18.21 removed that command), followed by `bind "?" _atuin_ai_question_mark`.
|
||||
|
||||
## Key bindings
|
||||
|
||||
`functions/fish_user_key_bindings.fish` turns on **vi mode** (`fish_vi_key_bindings`) first, then adds custom binds in both `insert` and `default` (normal) modes, so they work in either mode.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>ctrl-e</kbd> | `_tv_inline git-files`, then repaint: pick a file in the repo and open it in nvim ([television](../tools/television.md#git-files)) |
|
||||
| <kbd>ctrl-f</kbd> | `yy`: yazi, then cd to the directory yazi exited in |
|
||||
| <kbd>ctrl-b</kbd> | `purple`, then repaint (an external command; this repo does not provide it) |
|
||||
| <kbd>ctrl-y</kbd> | `clip-last`: re-run the last command and copy its output |
|
||||
| <kbd>ctrl-o</kbd> | `_gho_bind`: `gho` (pick a ghq repo or Obsidian vault from the `ghq-repos` cable and cd into it), then repaint |
|
||||
| <kbd>ctrl-p</kbd> | `_pj_bind`: `pj`, the task runner on the `pj` cable ([Scripts](scripts.md#pj)), then repaint |
|
||||
| <kbd>ctrl-a</kbd> | `omp-session`, then repaint: resume or start omp for the current repo by branch and session age, opening the `omp-sessions` cable only when nothing qualifies ([Scripts](scripts.md#omp-session)) |
|
||||
| <kbd>ctrl-n</kbd> | `omp`, then repaint: always a new omp session |
|
||||
| <kbd>ctrl-w</kbd> | `atuin ai inline`: natural-language command overlay |
|
||||
| <kbd>ctrl-z</kbd> | `_jobs_bind`: with one background job, `fg` it; with several, pick one from the `jobs` cable ([television](../tools/television.md#jobs)) and `fg` that; with none, nothing. While a job holds the terminal, ctrl-z is still the tty's suspend, so the key toggles suspend and resume |
|
||||
| <kbd>ctrl-g</kbd> | `lazygit` |
|
||||
| <kbd>down</kbd> | `_pj_bind_down`: opens the `pj` cable on an idle, empty prompt, otherwise fish's `down-or-search` |
|
||||
| <kbd>ctrl-c</kbd> | Clears the command line, runs `clear` (screen and kitty scrollback), repaints |
|
||||
| <kbd>ctrl-q</kbd> | `exit` |
|
||||
|
||||
Bindings added by tool init scripts:
|
||||
|
||||
| Key | Mode | Action | Source |
|
||||
| --- | --- | --- | --- |
|
||||
| <kbd>ctrl-r</kbd> | default and insert | `_atuin_search` | `atuin init fish` |
|
||||
| <kbd>up</kbd> | default and insert | `_atuin_bind_up` (atuin history, directory filter) | `atuin init fish` |
|
||||
| <kbd>?</kbd> | default (vi normal) | `_atuin_ai_question_mark`: on an empty buffer, opens `atuin ai inline` | `config.fish` |
|
||||
| <kbd>ctrl-t</kbd> | insert | `tv_smart_autocomplete` | `tv init fish` |
|
||||
|
||||
`_pj_bind_down` hands the key to `down-or-search` when the buffer is non-empty, the completion pager is open, or history search is active. Otherwise it runs `_pj_bind --quiet` (`PJ_QUIET=1`), so pressing down outside a project does nothing and prints no error.
|
||||
|
||||
The pickers run `tv --inline`, which opens on the line below the prompt and leaves the cursor there when it exits. The wrappers put the prompt back before `commandline -f repaint`, writing the cursor moves to `/dev/tty`:
|
||||
|
||||
| Wrapper | Runs | Then |
|
||||
| --- | --- | --- |
|
||||
| `_tv_inline ARGS` | `tv ARGS --inline` | `\033[A` (cursor up), returns tv's status |
|
||||
| `_gho_bind` | `gho` | `\033[A` if the cwd did not change; `\n` after a cd, so the prompt lands below onefetch |
|
||||
| `_pj_bind [--quiet]` | `pj` with `PJ_STATE` set to a temp file | `\033[A` for `open` (cancelled) or empty (no task sources); `\n` for `ran` (the task printed output) |
|
||||
| `_jobs_bind` | `fg` of the only job, or `jobs` with `TV_JOBS` set to a temp file holding the job table | `\033[A` (via `_tv_inline`) when the picker is cancelled |
|
||||
|
||||
`_jobs_bind` runs `fg` by replacing the command line with ` __jobs_bind_fg <id>` (leading space: kept out of history) and executing it, not from inside the binding. A binding runs while fish's reader still owns the terminal with extended keys on, so a job started with `&` that had never held the terminal got ctrl-z and ctrl-c as escape sequences instead of signals. `__jobs_bind_fg` erases the echoed prompt line (`\033[A\033[2K` on `/dev/tty`) and runs `fg` with its "Send job … to foreground" message sent to `/dev/null`, so resuming leaves nothing on screen. Text already typed is put back at the next prompt by a one-shot `fish_prompt` handler.
|
||||
|
||||
`omp-session` (<kbd>ctrl-a</kbd>) does its own `\033[A` when its `omp-sessions` picker is cancelled, so the binding only repaints.
|
||||
|
||||
zsh and bash bind <kbd>ctrl-o</kbd>, <kbd>ctrl-e</kbd>, <kbd>ctrl-p</kbd>, <kbd>down</kbd>, and <kbd>ctrl-t</kbd> the same way ([bash and zsh](bash-zsh.md)). Inside television, <kbd>ctrl-e</kbd> runs the channel's `edit` action and <kbd>ctrl-h</kbd> its `goto_parent_dir` action (`~/.config/television/config.toml`). The config and cables are on the [television](../tools/television.md) page.
|
||||
|
||||
> [!NOTE]
|
||||
> Under kitty, <kbd>ctrl-g</kbd> (lazygit overlay), <kbd>ctrl-y</kbd> (yazi split), and <kbd>ctrl-h/j/k/l</kbd> (split navigation) are mapped in `kitty.conf` and never reach fish. The fish binds for those keys only apply in other terminals. See [kitty](../terminal/kitty.md).
|
||||
|
||||
The prompt shows `❮` in green in vi normal mode (starship's `vimcmd_symbol`).
|
||||
|
||||
## Theme
|
||||
|
||||
`themes/cyberdream.theme` is a fish theme file with colors from [cyberdream.nvim](https://github.com/scottmckendry/cyberdream.nvim) and preferred background `#16181a`. The repo ships the file but does not select it. The active theme is stored in `fish_variables`, which chezmoi ignores, so pick it once per machine with `fish_config theme save cyberdream`.
|
||||
|
||||
| Variable | Color |
|
||||
| --- | --- |
|
||||
| `fish_color_normal` | `#ffffff` |
|
||||
| `fish_color_command` | `#5ef1ff` |
|
||||
| `fish_color_param`, `fish_color_escape` | `#ffaecf` |
|
||||
| `fish_color_keyword`, `fish_color_host` | `#5eff6c` |
|
||||
| `fish_color_quote`, `fish_color_option`, `fish_color_host_remote` | `#f1ff5e` |
|
||||
| `fish_color_redirection`, `fish_color_operator` | `#5ea1ff` |
|
||||
| `fish_color_end` | `#bd5eff` |
|
||||
| `fish_color_comment`, `fish_color_gray`, `fish_color_autosuggestion` | `#7b8496` |
|
||||
| `fish_color_error`, `fish_color_cancel`, `fish_color_status` | `#ff6e5e` |
|
||||
| `fish_color_selection`, `fish_color_search_match` | background `#3c4048` |
|
||||
| `fish_color_cwd` | `#ffbd5e` |
|
||||
| `fish_color_user` | `#5ef5d2` |
|
||||
| `fish_pager_color_progress`, `fish_pager_color_description` | `#7b8496` |
|
||||
| `fish_pager_color_prefix` | `#5ea1ff` |
|
||||
| `fish_pager_color_completion` | `#ffffff` |
|
||||
|
||||
`FZF_DEFAULT_OPTS` in `10-env.fish` uses the same palette.
|
||||
|
||||
## Completions
|
||||
|
||||
| File | Command | Mechanism |
|
||||
| --- | --- | --- |
|
||||
| `ccc.fish` | `ccc` | Typer-generated: calls `ccc` with `_CCC_COMPLETE=complete_fish` |
|
||||
| `grok.fish` | `grok` | Static clap-style completion script (global options, subcommands) |
|
||||
| `mise.fish` | `mise` | `mise completion fish \| source` (generated at load time) |
|
||||
| `nvm.fish` | `nvm` | From the nvm.fish plugin |
|
||||
| `omp.fish` | `omp` | `omp completions fish \| source` (generated at load time) |
|
||||
| `ostt.fish` | `ostt` | Static clap-style completion script |
|
||||
|
||||
## Functions
|
||||
|
||||
Every file in `functions/` is autoloaded by name. Several also exist as POSIX scripts in `~/.local/bin` for bash and zsh. In fish the function shadows the script. See [Scripts](scripts.md) for why the cd-style helpers must stay shell functions.
|
||||
|
||||
| Function | Purpose |
|
||||
| --- | --- |
|
||||
| `_nvm_index_update` | nvm.fish internal: downloads `index.tab` from `$nvm_mirror` into `$nvm_data/.index` |
|
||||
| `_nvm_list` | nvm.fish internal: lists installed Node versions plus `system` |
|
||||
| `_nvm_version_activate` | nvm.fish internal: sets `nvm_current_version` and prepends its `bin` to `PATH` |
|
||||
| `_nvm_version_deactivate` | nvm.fish internal: removes a version's `bin` from `PATH` |
|
||||
| `_gho_bind` | <kbd>ctrl-o</kbd> handler: `gho`, then cursor fix and repaint (see [Key bindings](#key-bindings)) |
|
||||
| `_pj_bind` | <kbd>ctrl-p</kbd> handler: `pj` with `PJ_STATE`, then cursor fix and repaint. `--quiet` sets `PJ_QUIET=1` |
|
||||
| `_jobs_bind` | <kbd>ctrl-z</kbd> handler: `fg` the only job or pick one from the `jobs` cable (see [Key bindings](#key-bindings)) |
|
||||
| `_pj_bind_down` | Down-arrow handler: `_pj_bind --quiet` on an empty idle prompt, else `down-or-search` |
|
||||
| `_tv_inline` | `tv $argv --inline`, then moves the cursor up one line on `/dev/tty` |
|
||||
| `ai` | `gum choose` among `claude`, `opencode`, `codex`, `hermes`, `fractal`. `claude` runs with `--dangerously-skip-permissions` |
|
||||
| `c` | `clear` with no argument, `cd <dir>` with one |
|
||||
| `c2p` | fzf-pick a `.hbs` template under `~/.config/code2prompt` and run `code2prompt --template` |
|
||||
| `cd` | `builtin cd`. If that fails, `zoxide query` the argument. Every successful cd is recorded with `zoxide add` |
|
||||
| `clear` | Prints `ESC[2J ESC[3J ESC[1;1H` to clear the screen and kitty scrollback, then re-pins the prompt if `FISH_BOTTOM_PROMPT=1`. Falls back to `command clear` when not interactive |
|
||||
| `clip-last` | Re-runs `$history[1]`, pipes the output to `cpy`, and sends a `notify-send` notification |
|
||||
| `cp-appid` | fzf-pick a `.desktop` app ID and copy it with `wl-copy` (desktop helper) |
|
||||
| `czs` | chezmoi sync: `chezmoi status`, `chezmoi re-add` of drifted files, commit the source repo, then `git pull --rebase --autostash` and `git push` if ahead. Optional targets scope it (for example `czs ~/.config/fish`). Messages print with a `chs:` prefix |
|
||||
| `extract_agents <dir>` | Copies every `AGENTS.md` under the current tree into `<dir>`, preserving paths |
|
||||
| `fab` | fzf-pick a Fabric pattern from `~/.config/fabric/pattern_explainations.json` (description in the preview, <kbd>ctrl-/</kbd> toggles it) and run it, feeding piped stdin if any |
|
||||
| `fish_title` | Terminal title: `prompt_pwd`, or `<host (10 chars)>:<pwd>` over SSH |
|
||||
| `fish_user_key_bindings` | Vi mode plus the custom binds above |
|
||||
| `gdc` | `gum confirm`, then `git reset --hard HEAD` and `git clean -fd` |
|
||||
| `gh-sonr-org-migrate` | Script body (see the warning below) that moves every repo from `*sonr*` GitHub orgs into `sonr-io` and deletes the emptied orgs |
|
||||
| `ghg <repo>` | `ghg-clone <repo>` (ghq get), cd into the clone, `clear`, `onefetch` |
|
||||
| `ghn <name>` | Runs `~/.local/bin/ghn` to create and clone a private repo, then cds into it |
|
||||
| `gho` | `tv ghq-repos --inline` (repo/vault picker); returns if nothing was picked, else cds into the result, `clear`, and `onefetch` inside git repos |
|
||||
| `git-summary [flags]` | `code2prompt` over the repo root, including only `AGENTS.md` files, using `~/.config/code2prompt/agents-md.hbs`. Extra flags pass through |
|
||||
| `gtm [repo]` | Migrates GitHub repos to a prad.codes (Gitea) org with `tea repos migrate`. With no argument, fzf multi-selects from `gh repo list`. You pick the target org and migration options (wiki, issues, labels, pull-requests, releases, milestones, lfs, mirror) with gum, private repos stay private, and it offers to clone the results with `ghg-clone`. Fish only |
|
||||
| `launch_omp_livediff_overlay` | Uses `kitty @` to overlay `omp` on the current window with a `livediff` vsplit beside it. Needs `KITTY_LISTEN_ON` |
|
||||
| `lc-elir [root]` | fzf multi-select numbered LeetCode solution files (`1234.slug.js`/`.ts`) and run `omp -p "/leetcode-eli5 <file>"` on each |
|
||||
| `mdcp <url>` | `curl.md <url>`, copy the result with `cpy`, and preview it with `mdfried` |
|
||||
| `mdp` | fzf-pick a Markdown file up to 3 levels deep (mdfried preview) and open it in `mdfried` |
|
||||
| `nvm` | nvm.fish: `install`, `use`, `list`, `list-remote`, `current`, `uninstall`. Reads `.nvmrc`/`.node-version` |
|
||||
| `y` | yazi with `--cwd-file`, then cd to the directory yazi exited in |
|
||||
| `yy` | Same as `y`. Used by the <kbd>ctrl-f</kbd> bind |
|
||||
|
||||
Functions defined inside `conf.d/` and `config.fish` rather than `functions/`: `q` (20-aliases), `terminal_title` (50-terminal-title), the `pnpmf*` helpers and `__pnpm_pick_pkg` (pnpm-filter), the `_nvm_install`/`_nvm_update`/`_nvm_uninstall` event handlers (nvm), and `starship_transient_prompt_func`, `starship_transient_rprompt_func`, and `_atuin_ai_question_mark` (config.fish).
|
||||
|
||||
> [!WARNING]
|
||||
> **gh-sonr-org-migrate**
|
||||
>
|
||||
> `functions/gh-sonr-org-migrate.fish` is a script, not a function definition. Running `gh-sonr-org-migrate` in fish autoloads the file, which runs the whole fish migration flow. Because the file defines no function of that name, fish then falls through to the bash port at `~/.local/bin/gh-sonr-org-migrate` and starts a second run. Cancel the second run at its first `gum` prompt, or call the bash script by its full path.
|
||||
|
||||
## auto-Niri.fish
|
||||
|
||||
`auto-Niri.fish` ships in the shared fish submodule and starts niri from tty1. This repo does not run niri, so `.chezmoiignore.tmpl` lists `.config/fish/auto-Niri.fish` and it is never applied to `$HOME`. It is not in `conf.d/`, so fish would not load it automatically either way.
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
# Scripts
|
||||
|
||||
The shell-agnostic executables in ~/.local/bin — usage, behavior, and dependencies for each.
|
||||
|
||||
`dot_local/bin/` holds standalone `sh`, `bash`, and `python3` executables. The `executable_` prefix is a chezmoi attribute that sets the executable bit and is dropped from the target name, so `dot_local/bin/executable_pj` becomes `~/.local/bin/pj`. bash, zsh, and fish all put `~/.local/bin` on `PATH`, so every shell runs the same implementation.
|
||||
|
||||
This page covers the general-purpose scripts. The Umbriel scripts in the same directory (`umbriel-launch`, `umbriel-smart-move`, `umbriel-float-square`) are documented on the [Umbriel](../desktop/umbriel.md) page.
|
||||
|
||||
## Why some helpers are shell functions
|
||||
|
||||
A script runs as a child process, and a child cannot change its parent shell's working directory or read the parent's in-memory history. Helpers that need to do either are therefore defined as shell functions, ported three times: `fish/functions/*.fish`, `bash/functions/*.bash`, and `zsh/functions/*.zsh`.
|
||||
|
||||
| Helper | Why it must be a function |
|
||||
| --- | --- |
|
||||
| `yy` (and fish's `y`) | cds into the directory yazi exited in |
|
||||
| `c` | cds when given an argument |
|
||||
| `cd` | Replaces the `cd` builtin with a zoxide fallback |
|
||||
| `ghg`, `ghn`, `gho` | cd into a repo that was picked or cloned |
|
||||
| `clip-last` | Reads the shell's own history (`$history[1]` in fish, `fc` in bash/zsh) |
|
||||
|
||||
For `ghg` and `ghn`, the work lives in a script that prints a directory on stdout (and everything else on stderr). The shell function captures that path and cds into it. `ghg`'s script is named `ghg-clone` because an unrelated cargo binary already uses the name `ghg`. `gho` has no script: the function reads the path from `tv ghq-repos` ([television](../tools/television.md#ghq-repos)).
|
||||
|
||||
Several scripts below also exist as fish functions of the same name (`ai`, `c2p`, `extract_agents`, `fab`, `gdc`, `git-summary`, `lc-elir`, `mdcp`, `mdp`, `launch_omp_livediff_overlay`, `gh-sonr-org-migrate`). In fish the function runs instead of the script. Both versions behave the same. See [fish functions](fish.md#functions).
|
||||
|
||||
## Overview
|
||||
|
||||
| Script | Language | One line |
|
||||
| --- | --- | --- |
|
||||
| [`ai`](#ai) | sh | Pick an AI CLI with gum and launch it |
|
||||
| [`atuin-pick`](#atuin-pick) | python3 | atuin history picker for a kitty or tmux split |
|
||||
| [`brave-new-tab`](#brave-new-tab) | sh | Open a URL in a new Brave Origin tab |
|
||||
| [`c2p`](#c2p) | sh | code2prompt with an fzf-picked template |
|
||||
| [`extract_agents`](#extract_agents) | sh | Copy all `AGENTS.md` files into a directory tree |
|
||||
| [`fab`](#fab) | sh | fzf picker for Fabric patterns |
|
||||
| [`gdc`](#gdc) | sh | Discard all git changes after confirmation |
|
||||
| [`gh-sonr-org-migrate`](#gh-sonr-org-migrate) | bash | Consolidate `*sonr*` GitHub orgs into `sonr-io` |
|
||||
| [`ghg-clone`](#ghg-clone) | sh | `ghq get` and print the clone path |
|
||||
| [`ghn`](#ghn) | sh | Create a private repo, clone it, print the path |
|
||||
| [`git-summary`](#git-summary) | sh | Aggregate a repo's `AGENTS.md` files with code2prompt |
|
||||
| [`gop`](#gop) | sh | Open the current repo's web page |
|
||||
| [`kitty-day-save`](#kitty-day-save) | bash | Save kitty's daily session right before shutdown/reboot |
|
||||
| [`launch_omp_livediff_overlay`](#launch_omp_livediff_overlay) | sh | omp overlay plus a livediff split in kitty |
|
||||
| [`lc-elir`](#lc-elir) | sh | Run `/leetcode-eli5` via omp on picked solutions |
|
||||
| [`mandible-pick`](#mandible-pick) | bash | fzf-pick a command, then open it in mandible |
|
||||
| [`mdcp`](#mdcp) | sh | Fetch a URL as Markdown, copy it, preview it |
|
||||
| [`mdp`](#mdp) | sh | fzf Markdown picker with mdfried preview |
|
||||
| [`mtime-sort`](#mtime-sort) | sh + perl | Sort path lines newest-first by mtime |
|
||||
| [`omp-session`](#omp-session) | python3 | Resume or start omp for the current repo; backend of the `omp-sessions` cable |
|
||||
| [`pj`](#pj) | bash | Task runner across every project task source, on a tv cable |
|
||||
| [`projects-sync`](#projects-sync) | bash | Symlink every `~/Code` repo into `~/Projects` |
|
||||
| [`pj-palette`](#pj-palette) | bash | pj's tasks plus quick actions as a tmux-palette source |
|
||||
| [`tmux-sessions`](#tmux-sessions) | bash | Session/window/zoxide switcher as a tmux-palette source |
|
||||
| [`yazi-nvim-open`](#yazi-nvim-open) | sh | yazi opener that targets the host Neovim |
|
||||
|
||||
## ai
|
||||
|
||||
```sh
|
||||
ai
|
||||
```
|
||||
|
||||
Shows `gum choose --header 'AI CLI'` with `claude`, `opencode`, `codex`, `hermes`, and `fractal`, then `exec`s the choice. `claude` runs as `claude --dangerously-skip-permissions`; the others run as the bare binary name. Esc or Ctrl-C exits 0.
|
||||
|
||||
**Needs:** `gum` and the chosen CLI.
|
||||
|
||||
## atuin-pick
|
||||
|
||||
```sh
|
||||
atuin-pick
|
||||
```
|
||||
|
||||
Opens atuin's interactive search (`atuin search -i --filter-mode directory`), limited to history from the current directory. It then `exec`s an interactive `fish`. If you picked a command, the new fish runs `echo '❯ <cmd>'; <cmd>` via `-C` (`--init-command`), so aliases resolve, a `cd` persists, and the prompt appears below the output. Esc starts a plain fish.
|
||||
|
||||
It is written for launch contexts where atuin's shell init never ran. It runs `atuin` from `PATH` and seeds `ATUIN_SESSION` from `atuin uuid` if unset.
|
||||
|
||||
**Launched by:** <kbd>ctrl+shift+p</kbd> in kitty. `kitty.conf` opens it in a horizontal split (`launch_hs atuin-pick`). Under the kitty-tmux profile, `tmux.conf` runs it with `tmux split-window -v -l 25%` in the current pane's directory. See [kitty](../terminal/kitty.md) and [atuin](atuin.md).
|
||||
|
||||
**Needs:** `python3`, `atuin`, `fish`.
|
||||
|
||||
## brave-new-tab
|
||||
|
||||
```sh
|
||||
brave-new-tab <url>
|
||||
```
|
||||
|
||||
Runs `brave-origin --new-tab <url>` detached with `setsid -f`. With Brave Origin running, the URL opens as a tab in its last active window; otherwise Brave Origin starts with it. Exits 2 unless given exactly one argument.
|
||||
|
||||
A URL without a scheme gets one from its host (port and path ignored): `localhost`, `*.localhost`, `*.local`, and `127.*` get `http://`, everything else `https://`. URLs containing `://`, and `about:` URLs, pass through unchanged.
|
||||
|
||||
| Argument | Opens |
|
||||
| --- | --- |
|
||||
| `example.com/path` | `https://example.com/path` |
|
||||
| `localhost:3000` | `http://localhost:3000` |
|
||||
| `printer.local` | `http://printer.local` |
|
||||
| `http://example.com` | `http://example.com` |
|
||||
|
||||
**Needs:** `brave-origin` (`brave-origin-bin`), `setsid` (util-linux).
|
||||
|
||||
## c2p
|
||||
|
||||
```sh
|
||||
c2p
|
||||
```
|
||||
|
||||
Lists `*.hbs` files under `~/.config/code2prompt` with `fd`, lets you pick one with fzf, and `exec`s `code2prompt --template <file>`.
|
||||
|
||||
**Needs:** `fd`, `fzf`, `code2prompt`.
|
||||
|
||||
## extract_agents
|
||||
|
||||
```sh
|
||||
extract_agents <output-dir>
|
||||
```
|
||||
|
||||
Creates `<output-dir>` and copies every `AGENTS.md` below the current directory into it with `cp --parents`, keeping the relative paths.
|
||||
|
||||
**Needs:** `fd`, GNU `cp` (`--parents`).
|
||||
|
||||
## fab
|
||||
|
||||
```sh
|
||||
fab
|
||||
some-command | fab
|
||||
```
|
||||
|
||||
Reads `~/.config/fabric/pattern_explainations.json`. fzf lists pattern names, the preview pane (right, 60%, wrapped) shows each description, and <kbd>ctrl-/</kbd> toggles the preview. The picked pattern runs as `fabric-ai --pattern <name>`, falling back to `fabric` if `fabric-ai` is not installed. Piped stdin is buffered to a temp file and fed to the pattern; the temp file is removed on exit.
|
||||
|
||||
**Needs:** `jq`, `fzf`, `fabric-ai` or `fabric`, and the pattern JSON.
|
||||
|
||||
## gdc
|
||||
|
||||
```sh
|
||||
gdc
|
||||
```
|
||||
|
||||
Asks `gum confirm "Discard all uncommitted changes? This is irreversible."`. On yes, runs `git reset --hard HEAD` and `git clean -fd`.
|
||||
|
||||
**Needs:** `gum`, `git`.
|
||||
|
||||
## gh-sonr-org-migrate
|
||||
|
||||
```sh
|
||||
gh-sonr-org-migrate
|
||||
```
|
||||
|
||||
A bash port of `fish/functions/gh-sonr-org-migrate.fish`, with the same flow:
|
||||
|
||||
1. **Preflight**: Checks that `gh`, `gum`, and `jq` exist and that `gh auth status` succeeds.
|
||||
2. **Discover**: Lists your GitHub orgs whose login contains `sonr`, excluding the target `sonr-io`. You pick which to migrate with `gum choose --no-limit`.
|
||||
3. **Transfer**: For each repo in each org, runs `gh api -X POST repos/<org>/<repo>/transfer -f new_owner=sonr-io`. If the name already exists in `sonr-io`, `gum input` asks for a new name (default `<org>-<repo>`). An empty name skips the repo.
|
||||
4. **Settle**: Waits 30 seconds (`gum spin`), because GitHub transfers are asynchronous.
|
||||
5. **Delete**: For every selected org that had no failed transfers and now has zero repos, asks `gum confirm` and then `gh api -X DELETE orgs/<org>`.
|
||||
|
||||
**Needs:** `gh` authenticated as owner of every org involved, `gum`, `jq`.
|
||||
|
||||
> [!WARNING]
|
||||
> In fish, run this script by its full path (`~/.local/bin/gh-sonr-org-migrate`). The fish file of the same name is a script body with no function in it, so typing just the name runs the fish flow and then this script. See [fish](fish.md#functions).
|
||||
|
||||
## ghg-clone
|
||||
|
||||
```sh
|
||||
ghg-clone <repo>
|
||||
```
|
||||
|
||||
Runs `ghq get <repo>`, with its output sent to stderr. It then works out the on-disk path: it strips the scheme, any `user@`, the scp-style colon, and a trailing `.git`, and prefixes `github.com/` when the input has no host. It checks that `$(ghq root)/<host>/<user>/<repo>` exists, runs `zoxide add` on it if zoxide is installed, and prints the path.
|
||||
|
||||
Accepts `https://…`, `ssh://…`, `git@host:user/repo`, `host/user/repo`, or `user/repo`.
|
||||
|
||||
**Mirrors.** After the clone, it sets up a [prad.codes](../tools/git.md) mirror when the repo has none. All of it talks on stderr and never fails the clone:
|
||||
|
||||
| Cloned from | Mirror | Skipped when |
|
||||
| --- | --- | --- |
|
||||
| `github.com/<owner>/<name>` | Pull mirror into a prad.codes org, every 8h (`tea repos migrate --mirror --mirror-interval 8h`, authenticated with `gh auth token`, private if the GitHub repo is). `gum choose` picks the org, preselected from GitHub's primary language: Go → `go`, Python → `py`, Rust → `rs`, Dart → `dart`, TeX → `tex`, JS/TS/Astro/Svelte/Vue/HTML/CSS → `js`, Markdown/MDX → `md`, anything else → `env`. Esc skips | You are not an admin of the GitHub repo, or a prad.codes repo already pulls from it (`original_url`) |
|
||||
| `prad.codes/<org>/<name>` | Push mirror to `github.com/<you>/<name>` on every push and every 8h, with a first sync right away. After a `gum confirm`, it creates the GitHub repo (same visibility) if missing; an existing one is overwritten by the mirror pushes, which the prompt says | The repo is itself a pull mirror, or already has a push mirror |
|
||||
|
||||
Both mirrors store your `gh` OAuth token in prad.codes as the remote credential. Without a terminal (no `gum` prompt), nothing is mirrored.
|
||||
|
||||
**Used by:** the `ghg` shell function, which cds into the path, clears the screen, and runs `onefetch`. The fish function `gtm` also uses it to clone migrated repos.
|
||||
|
||||
**Needs:** `ghq` (`GHQ_ROOT=~/Code`, exported by fish, zsh, and bash), optional `zoxide`. Mirrors need `gh`, `tea` (logged in to prad.codes), `jq`, and `gum`; without any of them the step is skipped.
|
||||
|
||||
## ghn
|
||||
|
||||
```sh
|
||||
ghn <repo-name>
|
||||
```
|
||||
|
||||
Creates a new **private** repo and clones it:
|
||||
|
||||
- **prad.codes** (Gitea): pick an org from `tea organizations list`, then `tea repos create --private` and `ghq get https://prad.codes/<org>/<name>`.
|
||||
- **github.com**: pick the owner `prdlk` or `wltbs`, then `gh repo create <owner>/<name> --private` and `ghq get`.
|
||||
|
||||
You pick the host with `gum choose`. The script prints the clone directory, and the `ghn` shell function cds into it.
|
||||
|
||||
**Needs:** `gum`, `ghq`, and `tea` or `gh`.
|
||||
|
||||
## git-summary
|
||||
|
||||
```sh
|
||||
git-summary [code2prompt flags…]
|
||||
```
|
||||
|
||||
From anywhere inside a git repo, runs `code2prompt <repo-root> --include AGENTS.md --include "**/AGENTS.md" --template ~/.config/code2prompt/agents-md.hbs`. Extra flags such as `--output-file` or `--tokens` are passed through.
|
||||
|
||||
**Needs:** `git`, `code2prompt`, and the `agents-md.hbs` template.
|
||||
|
||||
## gop
|
||||
|
||||
```sh
|
||||
gop # origin, else upstream, else first remote
|
||||
gop upstream # a named remote
|
||||
gop owner/repo # github.com/owner/repo
|
||||
gop <url> # any https/ssh/scp remote URL
|
||||
```
|
||||
|
||||
Opens a repo's web page for any forge whose URL layout is `https://<host>/<owner>/<repo>`, such as github.com and prad.codes. It reduces any remote form to `<host>/<owner>/<repo>`: it drops credentials, drops the port on `ssh://` and `git://` remotes, keeps the port and plain `http` on `http://` remotes, and strips `.git`. It then runs `$BROWSER` (default `xdg-open`) on the result.
|
||||
|
||||
**Needs:** `git`, a browser or `xdg-open`.
|
||||
|
||||
## kitty-day-save
|
||||
|
||||
```sh
|
||||
kitty-day-save # hold a logind delay lock, flush on shutdown, exit
|
||||
kitty-day-save --now # flush every running kitty immediately
|
||||
```
|
||||
|
||||
Run by `kitty-day-save.service` (`dot_config/systemd/user/`, `WantedBy=graphical-session.target`, `Restart=always` after 10 s). It wraps itself in `systemd-inhibit --what=shutdown --mode=delay` and waits on `gdbus monitor` for login1's `PrepareForShutdown (true,)`. logind sends that before anything is killed and waits for the lock (`InhibitDelayMaxSec`, 5 s by default), so every kitty still has all its windows. It then sends `kitten day_session.py flush` over remote control to each `@kitty-<pid>` socket in `/proc/net/unix` (3 s timeout each) and exits, which releases the lock. The kitty that owns today's session saves it and holds further saves for 60 s; other kitties ignore the flush. See [kitty daily sessions](../terminal/kitty.md#daily-sessions).
|
||||
|
||||
**Needs:** `systemd-inhibit`, `gdbus`, `kitten`. Linux desktop/laptop roles only (`.chezmoiignore.tmpl`).
|
||||
|
||||
## launch_omp_livediff_overlay
|
||||
|
||||
```sh
|
||||
launch_omp_livediff_overlay
|
||||
```
|
||||
|
||||
Inside kitty with remote control enabled, runs `kitty @ launch --type=overlay-main --cwd=current omp`, then opens `livediff` in a `vsplit` next to that window with `--keep-focus`. It relies on `$KITTY_LISTEN_ON` rather than a hard-coded socket, because kitty appends `-PID` to `listen_on` socket names. It exits with an error if the variable is unset.
|
||||
|
||||
**Needs:** kitty with `allow_remote_control`/`listen_on` ([kitty](../terminal/kitty.md)), `omp` ([AI agents](../ai/README.md)), `livediff`.
|
||||
|
||||
## lc-elir
|
||||
|
||||
```sh
|
||||
lc-elir [root]
|
||||
```
|
||||
|
||||
Finds numbered LeetCode solution files (`^\d+\..+\.(js|ts)$`, for example `1365.some-slug.js`) under `root` (default `.`). fzf multi-selects them, showing the last three path segments with a `bat` preview of the first 40 lines. Each pick runs `omp -p "/leetcode-eli5 <file>"`.
|
||||
|
||||
**Needs:** `fd`, `fzf`, `bat`, `omp`, and the `/leetcode-eli5` omp command.
|
||||
|
||||
## mandible-pick
|
||||
|
||||
```sh
|
||||
mandible-pick
|
||||
```
|
||||
|
||||
`mandible` (installed with cargo, `~/.cargo/bin`) needs a tool to open, so this asks for one first. fzf lists the executables installed in the `PATH` directories, without shell builtins, keywords, or aliases. <kbd>enter</kbd> opens the highlighted command. If the query contains a space (`git remote add`) or matches nothing, <kbd>enter</kbd> uses it exactly as typed, so mandible opens at that subcommand path. Then it `exec`s `mandible <tool> [subcommand…]`. <kbd>esc</kbd> exits quietly.
|
||||
|
||||
**Used by:** the tmux-palette **mandible** item (alias `man`), which runs it in a popup. See [tmux](../terminal/tmux.md#command-palette).
|
||||
|
||||
**Needs:** `mandible`, `fzf` 0.51+ (`--with-shell`, `transform`, `accept-or-print-query`).
|
||||
|
||||
## mdcp
|
||||
|
||||
```sh
|
||||
mdcp <url>
|
||||
```
|
||||
|
||||
Fetches the URL as Markdown with `curl.md`, copies it with `wl-copy`, then pastes it back into `mdfried` for a rendered preview.
|
||||
|
||||
**Needs:** `curl.md`, `mdfried`, and `wl-clipboard`.
|
||||
|
||||
## mdp
|
||||
|
||||
```sh
|
||||
mdp
|
||||
```
|
||||
|
||||
Lists `*.md` files up to 3 directories deep with `fd`. fzf shows an `mdfried` preview (right, 60%, wrapped), and the pick opens in `mdfried`.
|
||||
|
||||
**Needs:** `fd`, `fzf`, `mdfried`.
|
||||
|
||||
## projects-sync
|
||||
|
||||
```sh
|
||||
projects-sync
|
||||
```
|
||||
|
||||
Mirrors every repo under the ghq root (`$GHQ_ROOT`, default `~/Code`, laid out `<host>/<owner>/<repo>`) as `~/Projects/<repo>` → the repo. Repos with the same name under different owners become `<owner>-<repo>`. Links pointing into `~/Code` whose repo is gone are removed; real files, real directories, and links pointing elsewhere in `~/Projects` are never touched.
|
||||
|
||||
Run by `projects-sync.timer` (`dot_config/systemd/user/`, 30 s after login and every 5 minutes), which [`30-user-services`](../chezmoi/scripts.md#30-user-services) enables. ghq clones land two levels below `~/Code`, out of reach of a non-recursive `.path` unit, hence the poll.
|
||||
|
||||
**Needs:** GNU `find`. Linux desktop/laptop roles only (`.chezmoiignore.tmpl`).
|
||||
|
||||
## mtime-sort
|
||||
|
||||
```sh
|
||||
… | mtime-sort [--repo]
|
||||
```
|
||||
|
||||
Reads lines on stdin and prints them newest-first by the mtime of each line's last tab-separated field (the whole line when it has no tab). With `--repo`, it stats `<path>/.git/index` first, so a repo sorts by its last git activity. Lines whose path does not exist are dropped. Ties keep input order.
|
||||
|
||||
**Used by:** every custom [television](../tools/television.md) cable except `agents` and `omp-sessions`, and `pj --list`. tv layers match quality and frecency on top of this order.
|
||||
|
||||
**Needs:** `perl`.
|
||||
|
||||
## omp-session
|
||||
|
||||
```sh
|
||||
omp-session
|
||||
```
|
||||
|
||||
Starts or resumes omp for the current git repo by fixed rules, so <kbd>ctrl-a</kbd> never asks when the answer is obvious.
|
||||
|
||||
| HEAD | Does |
|
||||
| --- | --- |
|
||||
| Default branch | Resumes the newest session on it when that session was written less than 3 hours ago. Otherwise opens the `omp-sessions` [television](../tools/television.md#omp-sessions) cable over all of the repo's sessions; <kbd>enter</kbd> resumes the pick, <kbd>esc</kbd> returns to the prompt. With no sessions at all it starts a new one |
|
||||
| Any other branch | Resumes the newest session on that branch, or starts a new one |
|
||||
| Detached | Opens the `omp-sessions` cable (a new session when there are none) |
|
||||
| Outside a git repo | Plain `omp` |
|
||||
|
||||
Resuming runs `omp --resume <session file>`; omp switches to the session's own cwd. `omp-session --new` runs plain `omp`; it is the cable's <kbd>ctrl-n</kbd> action and marks `OMP_SESSION_STATE` like a resume does, so the prompt is not moved after a new session.
|
||||
|
||||
**Sessions.** Every `*/*.jsonl` under `$PI_CODING_AGENT_DIR/sessions` (default `~/.omp/agent/sessions`) whose `session` header `cwd` is the worktree root or below it, excluding worktrees nested inside it. Session age is the file's mtime (the last write).
|
||||
|
||||
**Branches.** omp does not record the git branch. A session's branch is the one HEAD was on when the file was last written, replayed from the worktree's `git reflog` (`checkout: moving from A to B`, `Branch: renamed`, `rebase (finish): returning to`). Sessions older than the oldest reflog move belong to the branch that move left; with no moves they all belong to the current branch. The default branch is `origin/HEAD`, else `init.defaultBranch`, `main`, or `master`, whichever exists.
|
||||
|
||||
**Entry points.** The cable calls back into the script:
|
||||
|
||||
| Command | Does |
|
||||
| --- | --- |
|
||||
| `omp-session --list` | Prints `<age> <branch> <title>[ · <subdir>]<TAB><session file>` per session, newest first. The title is omp's session title, else the first user message |
|
||||
| `omp-session --preview FILE` | Title, last-write time, cwd, then the text of every user and assistant message (thinking, tool calls, and tool results skipped) |
|
||||
| `omp-session --resume FILE` | Writes `ran` to `$OMP_SESSION_STATE` when set, then `exec omp --resume FILE` (enter) |
|
||||
|
||||
**Prompt redraw.** The launcher runs `tv omp-sessions --inline` with `OMP_SESSION_STATE` set to a temp file. When the picker is cancelled (no `ran`), it prints `\033[A` to `/dev/tty` so the shell redraws the prompt in place; the key bindings just repaint.
|
||||
|
||||
**Launched by:** <kbd>ctrl-a</kbd> in fish and zsh. <kbd>ctrl-n</kbd> at the prompt runs `omp` directly, not this script.
|
||||
|
||||
**Needs:** `python3`, `git`, `omp`, `tv`.
|
||||
|
||||
## pj
|
||||
|
||||
```sh
|
||||
pj [extra args…]
|
||||
```
|
||||
|
||||
A task runner that searches every task source in the project and lets you pick one in the `pj` [television](../tools/television.md#pj) cable.
|
||||
|
||||
**Sources.** Each is listed only when its runner is installed:
|
||||
|
||||
| Source | Found in | Runner |
|
||||
| --- | --- | --- |
|
||||
| npm scripts | `package.json` | package manager from `packageManager`, else from the lockfile (`bun.lock[b]`, `pnpm-lock.yaml`, `yarn.lock`, `package-lock.json`, searched upward), else `npm`, then `<pm> run` |
|
||||
| Make | `GNUmakefile`, `makefile`, `Makefile` | `make`. Targets come from a text parse of rules and `.PHONY`; `make -p` is avoided because it would run `$(shell …)` |
|
||||
| just | `justfile`, `.justfile`, `Justfile`, `JUSTFILE` | `just` |
|
||||
| mise | `mise.toml`, `.mise.toml`, `mise/config.toml`, `.mise/config.toml`, `.config/mise/config.toml`, or a `mise-tasks`/`.mise/tasks`/`mise/tasks` dir | `mise run`. Hidden tasks are skipped, and tasks inherited from parent directories are filtered out |
|
||||
| devbox | `devbox.json` `shell.scripts` | `devbox run` |
|
||||
| poe | `pyproject.toml` `[tool.poe.tasks]` | `poe`, else `uv run poe` |
|
||||
| pdm | `[tool.pdm.scripts]` (skips `_*` and `post_*`) | `pdm run` |
|
||||
| rye | `[tool.rye.scripts]` | `rye run` |
|
||||
| taskipy | `[tool.taskipy.tasks]` | `task`, else `uv run task` |
|
||||
| hatch | `[tool.hatch.envs.<env>.scripts]` as `env:name` | `hatch run` |
|
||||
| console scripts | `[tool.poetry.scripts]`, `[project.scripts]` | `poetry run` for Poetry projects, else `uv run` |
|
||||
|
||||
**Scanning.** Walks from `$PWD` up to the git toplevel, inclusive. Outside a repo it stops at the first directory that produced tasks.
|
||||
|
||||
**Picker.** `pj` writes the records to a temp file and runs `tv pj --inline --source-command "cat <tmp>" --input-header <project root>`. Rows are tagged with the tool (`npm`, `pnpm`, `bun`, `make`, `just`, `mise`, `poe`, `py`, …), so typing a tool name filters to it. A directory column appears when more than one project contributed tasks. Tasks are sorted by their manifest's mtime, newest first, through `mtime-sort`. The preview shows the task body: the npm script string, the Make recipe, `just --show`, `mise tasks info`, the devbox script, or the pyproject entry.
|
||||
|
||||
**Entry points.** The cable calls back into the script:
|
||||
|
||||
| Command | Does |
|
||||
| --- | --- |
|
||||
| `pj --list` | Prints tab-separated records: display, kind, target, root, runner, manifest |
|
||||
| `pj --preview KIND TARGET ROOT FILE` | Prints the task body for the preview pane |
|
||||
| `pj --run KIND TARGET ROOT RUNNER` | Runs the task (enter) |
|
||||
|
||||
**Run.** cds into the task's directory and `exec`s the runner. Extra arguments given to `pj` travel to the task through `$PJ_EXTRA` (shell-quoted), with `--` inserted for `npm run`, `mise`, and `devbox`. Tasks named `dev`, `start`, `serve`, `watch`, or namespaced variants like `dev:web` are wrapped in `openlogs` (found on `PATH`, via `mise which`, or in mise's node installs) for a live log tail. Without openlogs they run unwrapped, with a note.
|
||||
|
||||
**Env.** `PJ_QUIET=1` suppresses the "no task sources found" error (exit status stays 1). The <kbd>down</kbd> bindings use it. `PJ_STATE=FILE` makes `pj` write `open` to `FILE` before launching the picker, and `pj --run` writes `ran`; the file stays empty when there are no task sources. The key bindings read it to tell no tasks, a cancelled picker, and a task that printed output apart and redraw the prompt to match. `pj` itself does no cursor handling, so typing `pj` behaves like any command.
|
||||
|
||||
**Launched by:** <kbd>ctrl-p</kbd>, and <kbd>down</kbd> on an empty prompt, in fish, zsh, and bash.
|
||||
|
||||
**Needs:** `bash`, `tv`, `mtime-sort`, `jq`, `python3` 3.11+ (for `tomllib`), plus whichever runners the project uses.
|
||||
|
||||
## pj-palette
|
||||
|
||||
```sh
|
||||
pj-palette
|
||||
pj-palette --run KIND TARGET ROOT RUNNER
|
||||
pj-palette --cmd ROOT CMD…
|
||||
pj-palette --bg NAME [ROOT CMD…]
|
||||
pj-palette --omp
|
||||
```
|
||||
|
||||
The source of the **Project Tasks** [tmux command palette](../terminal/tmux.md#command-palette) (`palettes/tasks.json`). Open it with tmux `prefix t`, Ghostty <kbd>ctrl+shift+p</kbd> or <kbd>ctrl+shift+space</kbd>, or the "Project Tasks" item in the main palette.
|
||||
|
||||
With no arguments it reads the active pane's directory (`tmux display -p '#{pane_current_path}'`) and prints palette items as JSON, grouped by category:
|
||||
|
||||
| Group | Items | Shown when |
|
||||
| --- | --- | --- |
|
||||
| Running | Every background task session still alive (any project), by session name | A long-running item was started and not stopped |
|
||||
| Quick | `<pm> install` (alias `install`, `i`), `<pm> run dev` (`dev`) | `package.json` at the project root. The package manager comes from `packageManager`, then the lockfile, as in `pj` |
|
||||
| Quick | `uv sync` (`sync`), `uv run python` (`py`, `repl`) | `pyproject.toml` and `uv` installed |
|
||||
| Quick | `make` (default target; `m`, `build`) | A Makefile and `make` |
|
||||
| Quick | `go run .`, `cargo run` | `go.mod` / `Cargo.toml` and the toolchain |
|
||||
| Quick | `docker compose up` (`up`, `compose`) | `compose.y(a)ml` or `docker-compose.y(a)ml` at the project root and `docker` |
|
||||
| Tasks: `<tool>` | Every record from `pj --list`: npm/bun/pnpm/yarn scripts, Make, just, mise, devbox, pyproject task tables and console scripts | Whatever `pj` finds walking up to the git root |
|
||||
|
||||
The project root is the git toplevel, else the pane's directory.
|
||||
|
||||
**Run.** One-off items open a full-width split along the bottom of the window (`split-window -v -f -l 25%`), 25% of its height, in the task's directory. The command is passed to `split-window` as separate arguments, so no shell reparses it. `--run` calls `pj --run`, which keeps pj's `openlogs` wrapping of `dev`/`start`/`serve`/`watch` tasks, and `--cmd` runs a quick action. While the command runs, <kbd>ctrl-c</kbd> stops only the command (the script traps `SIGINT`), so the output stays. Once the command exits, the script prints `[exit N] enter or ctrl-c to close`, and <kbd>enter</kbd> or <kbd>ctrl-c</kbd> closes the pane; other keys are ignored.
|
||||
|
||||
**Background.** Long-running items, marked `· background`, are `<pm> run dev`, `docker compose up`, and pj tasks named `dev`/`start`/`serve`/`watch`/`preview` or with one of those as a `:` segment (`dev:web`, `api:dev`). They run through `--bg` in a detached session named `<session>:<tool-and-command>` (`leetcode:bun-run-dev`, `leetcode:docker-compose-up`), shown in a rounded 90%×80% popup attached to it. <kbd>q</kbd> detaches the popup and leaves the task running: the session's `key-table` is `pj-task`, which binds only `q` and <kbd>ctrl-c</kbd>, so other keys reach the task instead of root-table bindings (prefix bindings still work). <kbd>ctrl-c</kbd> runs `confirm-before` (`Stop <session>? (y/n)`); <kbd>y</kbd> sends ctrl-c to the task, which stops it and ends the session, closing the popup, and any other key cancels. If the task exits on its own, the `[exit N]` prompt keeps its output until <kbd>enter</kbd>. Picking the item again, or its **Running** entry, reopens the popup instead of starting a second copy. The session has `status off`, `detach-on-destroy on`, and `@pj_task` set to the task's root, which is how the Running group finds it. Session names contain `:`, so the script targets them by id (`$N`).
|
||||
|
||||
**omp.** tmux `prefix a` (Ghostty <kbd>ctrl+shift+a</kbd>) runs `pj-palette --bg <session>:omp <pane cwd> omp`, so omp gets the same kind of background session and Running entry. Its popup docks in the bottom-right corner of the client, 35% wide and 60% tall. omp runs directly (no `[exit N]` prompt) and needs <kbd>q</kbd>, so its session uses the `pj-app` key table instead: <kbd>ctrl-c</kbd> hides the popup, <kbd>ctrl-q</kbd> kills the session (quits omp). `prefix a` inside any session with `@pj_task` also hides, which makes <kbd>ctrl+shift+a</kbd> a toggle. The popup border shows only the session name.
|
||||
|
||||
**omp palette.** `--omp` feeds `palettes/omp.json`, opened by the main palette's **omp** item. It lists every running `<session>:omp` across tmux sessions, with a green icon and `open`/`hidden` state: **This session** first (or a grey `not running · start in <cwd>` item that starts it), then **Running** for the other tmux sessions' agents, then a **Quit** item per session (`kill-session`).
|
||||
|
||||
**Needs:** `pj` and its dependencies, `jq`, tmux.
|
||||
|
||||
## tmux-sessions
|
||||
|
||||
```sh
|
||||
tmux-sessions
|
||||
tmux-sessions --new QUERY
|
||||
tmux-sessions --dir DIR
|
||||
```
|
||||
|
||||
The source of the **Sessions** [tmux command palette](../terminal/tmux.md#command-palette) (`palettes/sessions.json`), which replaced tmux-sessionx. Open it with tmux `prefix o`, Ghostty <kbd>ctrl+shift+o</kbd> or <kbd>super+enter</kbd>, or the "Sessions" item in the main palette.
|
||||
|
||||
With no arguments it prints palette items as JSON, grouped by category:
|
||||
|
||||
| Group | Items | Action |
|
||||
| --- | --- | --- |
|
||||
| Sessions | Every session but the current one, the client's last session first. Attached ones have a green icon | `switch-client` |
|
||||
| Windows | Every window of every session, as `<session>:<index> · <cwd>` | `switch-client` to that window |
|
||||
| Zoxide | `zoxide query -l` | `--dir` |
|
||||
| Config | Directories in `~/.config` that zoxide does not list | `--dir` |
|
||||
| Rename | One per session | `command-prompt` prefilled with the name |
|
||||
| Kill | One per session except the current one | `kill-session` |
|
||||
|
||||
pj-palette's background task sessions (`@pj_task`) are left out. Sessions are targeted by id, since names may contain `:`.
|
||||
|
||||
**`--new QUERY`** is the palette's `query` item, the typed text listed after the matches. It switches to the session named QUERY if there is one. Otherwise it starts a session named QUERY in QUERY itself if that is a directory (via `--dir`), else in `zoxide query QUERY`'s match (QUERY's words are separate keywords), else in tmux's default directory. Typed text that matches nothing therefore lands on the `query` item, the same as sessionx's zoxide mode.
|
||||
|
||||
**`--dir DIR`** switches to, or starts in DIR, the session named after DIR's basename with dots removed (sessionx's rule).
|
||||
|
||||
**Needs:** tmux, `jq`, `zoxide`.
|
||||
|
||||
## yazi-nvim-open
|
||||
|
||||
```sh
|
||||
yazi-nvim-open [vsplit|split|tabedit] <file>…
|
||||
```
|
||||
|
||||
yazi's `edit` opener. When run inside a Neovim terminal (`$NVIM` set), each file's absolute path goes to the **host** Neovim through `nvim --server "$NVIM" --remote-expr 'v:lua.EditFromYazi(mode, path)'` (defined in the nvim config's `yazi_session.lua`). Outside Neovim it `exec`s a normal `nvim`, and the split modes do nothing. Set `YAZI_OPEN_TRACE=<file>` to log each call.
|
||||
|
||||
**Used by:** `yazi.toml` (the `edit` opener) and `keymap.toml` (<kbd>ctrl-v</kbd> vsplit, <kbd>ctrl-x</kbd> split). See [yazi](../tools/yazi.md).
|
||||
|
||||
**Needs:** `nvim`, `realpath`.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Starship
|
||||
|
||||
The cross-shell prompt — a minimal left prompt, a clock on the right, and a cyberdream palette.
|
||||
|
||||
[Starship](https://starship.rs) renders the prompt in fish, bash, and zsh from one config file.
|
||||
|
||||
| Source (repo) | Target |
|
||||
| --- | --- |
|
||||
| `dot_config/starship.toml` | `~/.config/starship.toml` |
|
||||
|
||||
The file is not a template and applies on every role. `starship` is in `.chezmoidata/packages.yaml` for pacman and is installed by `.chezmoiscripts/run_onchange_before_10-packages.sh.tmpl`.
|
||||
|
||||
## Shell integration
|
||||
|
||||
| Shell | Init |
|
||||
| --- | --- |
|
||||
| fish | `conf.d/40-tools.fish` runs `starship init fish`, then `config.fish` runs it again and calls `enable_transience` |
|
||||
| bash | `eval "$(starship init bash)"` in `~/.bashrc` |
|
||||
| zsh | `eval "$(starship init zsh)"` in `~/.zshrc`, plus a transient-prompt hook |
|
||||
|
||||
With transience in fish, past prompts in the scrollback collapse to the output of `starship_transient_prompt_func` (`starship module character`, the `❯` alone) and `starship_transient_rprompt_func` (`starship module time`). See [fish](fish.md#configfish).
|
||||
|
||||
zsh has no built-in transience, so `~/.zshrc` collapses past prompts itself, rendering the `transient` (`$character`) and `transient_right` (`$time`) entries under `[profiles]` with `starship prompt --profile`. It uses profiles rather than `starship module` because only `starship prompt` wraps color escapes in zsh's `%{…%}`; without them zsh miscounts the right prompt's width. zsh also gets starship's default `∙` continuation prompt (`PROMPT2`). See [bash and zsh](bash-zsh.md#zshrc).
|
||||
|
||||
## Layout
|
||||
|
||||
**~/.config/starship.toml**
|
||||
|
||||
```toml
|
||||
"$schema" = 'https://starship.rs/config-schema.json'
|
||||
palette = "cyberdream"
|
||||
|
||||
format = """
|
||||
$username\
|
||||
$hostname\
|
||||
$directory\
|
||||
$git_branch\
|
||||
$git_state\
|
||||
$git_status\
|
||||
$cmd_duration\
|
||||
$python\
|
||||
$character"""
|
||||
|
||||
right_format = """$time"""
|
||||
```
|
||||
|
||||
The left prompt is a single line with no line break before `$character`. Only the nine listed modules can appear; every other starship module is left out of `format`. `$username` and `$hostname` keep their defaults, so they appear only over SSH or as root.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Format | Style | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| `directory` | default | `blue` | |
|
||||
| `git_branch` | `[$branch]($style)` | `bright-black` | Branch name only, no symbol or "on" |
|
||||
| `git_state` | `\([$state( $progress_current/$progress_total)]($style)\) ` | `bright-black` | For example `(REBASING 2/5)` |
|
||||
| `git_status` | `[[(*$conflicted$untracked$modified$staged$renamed$deleted)](218) ($ahead_behind$stashed)]($style)` | `cyan` | See below |
|
||||
| `cmd_duration` | `[$duration]($style) ` | `yellow` | Default threshold |
|
||||
| `python` | `[$virtualenv]($style) ` | `bright-black` | `detect_extensions = []`, `detect_files = []` |
|
||||
| `time` | `[$time]($style)` | `bright-black` | `disabled = false`, `time_format = '%-I:%M %p'` (for example `3:07 PM`) |
|
||||
| `character` | default | | Symbols below |
|
||||
|
||||
**git_status.** The `conflicted`, `untracked`, `modified`, `staged`, `renamed`, and `deleted` symbols are all set to a zero-width space. Any dirty state therefore shows a single `*` in 256-color `218` (pink) instead of per-state counts. `stashed` is `≡`. `ahead_behind` keeps starship's default arrows and is drawn in `cyan`.
|
||||
|
||||
**python.** With both detect lists empty, the module does not trigger on `.py` files or `pyproject.toml`. It shows only the name of an active virtualenv.
|
||||
|
||||
### Prompt character
|
||||
|
||||
| State | Symbol |
|
||||
| --- | --- |
|
||||
| Last command succeeded | `❯` in `purple` |
|
||||
| Last command failed | `❯` in `red` |
|
||||
| Vi normal mode (fish) | `❮` in `green` |
|
||||
|
||||
fish runs in vi mode ([key bindings](fish.md#key-bindings)), so `vimcmd_symbol` appears there. zsh uses the emacs keymap and never shows it.
|
||||
|
||||
## Palette
|
||||
|
||||
`palette = "cyberdream"` selects the `[palettes.cyberdream]` table, colors from [cyberdream.nvim](https://github.com/scottmckendry/cyberdream.nvim). Every named color in the module styles resolves through this table.
|
||||
|
||||
| Name | Normal | `bright-*` | `dim-*` |
|
||||
| --- | --- | --- | --- |
|
||||
| black | `#1e2124` | `#4a4d4f` | `#1c1f22` |
|
||||
| red | `#ff6e5e` | `#ff8b7e` | `#d05c50` |
|
||||
| green | `#5eff6c` | `#7eff89` | `#4fd05b` |
|
||||
| yellow | `#f1ff5e` | `#f3ff7e` | `#c5d050` |
|
||||
| blue | `#5ea1ff` | `#7eb3ff` | `#4f85d1` |
|
||||
| purple | `#bd5eff` | `#ff7ef3` | `#d050c6` |
|
||||
| cyan | `#5ef1ff` | `#7ef3ff` | `#4fc5d1` |
|
||||
| white | `#ffffff` | `#ffffff` | `#d0d0d1` |
|
||||
|
||||
`bright-black` (`#4a4d4f`) is the muted gray used for the branch, git state, virtualenv, and clock. The `dim-*` entries are defined but no module uses them.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Terminal
|
||||
|
||||
Terminal emulators and multiplexers configured by the dotfiles.
|
||||
|
||||
kitty is the primary terminal. It runs in three modes: plain kitty (kitty owns splits and tabs), a tmux host, and a herdr host. Ghostty is a tmux-only terminal: every window attaches to tmux, and its `ctrl+shift` chords drive tmux.
|
||||
|
||||
| Page | Source | What it covers |
|
||||
| --- | --- | --- |
|
||||
| [kitty](kitty.md) | external repo via `.chezmoiexternals/kitty.toml` | Main config, host configs (tmux, herdr, goliath), kittens, custom tab bar |
|
||||
| [Ghostty](ghostty.md) | external repo via `.chezmoiexternals/ghostty.toml` | tmux-only terminal: ctrl+shift chords → tmux, themes |
|
||||
| [tmux](tmux.md) | external repo via `.chezmoiexternals/tmux.toml` | Prefix, bindings, kitty-style tabs with Nerd Font icons, resurrect/continuum persistence |
|
||||
| [herdr](herdr.md) | external repo via `.chezmoiexternals/herdr.toml`, plus `dot_config/herdr-nvim/` | Agent-aware multiplexer: keys, theme, plugins, Claude hook |
|
||||
|
||||
## Shared conventions
|
||||
|
||||
- Font: VictorMono Nerd Font everywhere.
|
||||
- Background opacity `0.9`, no window decorations; the compositor provides blur.
|
||||
- Prefix `ctrl+b` in tmux and herdr. kitty and Ghostty translate chords into the prefix byte `\x02` plus a key, so the same physical shortcut works across hosts.
|
||||
- `ctrl+h/j/k/l` moves between panes across nvim, kitty, tmux, and herdr (vim-aware handoff at every layer).
|
||||
@@ -0,0 +1,97 @@
|
||||
# Ghostty
|
||||
|
||||
Ghostty as a tmux-only terminal — every window attaches to tmux, and ctrl+shift chords drive tmux.
|
||||
|
||||
Ghostty is the default terminal and only a [tmux](tmux.md) client. tmux owns tabs (windows), splits (panes), sessions, and persistence. Ghostty draws the text and handles the clipboard and font size.
|
||||
|
||||
It is the default everywhere a desktop picks a terminal: `$TERMINAL` in the [Umbriel](../desktop/umbriel.md) session (Noctalia runs `Terminal=true` launcher entries with it), and the first entry of `~/.config/xdg-terminals.list` (`dot_config/xdg-terminals.list`, desktop roles only) for `xdg-terminal-exec`, and other launchers that open a terminal.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/ghostty](https://github.com/prdlk/ghostty) via `.chezmoiexternals/ghostty.toml`: `config`, `themes/` |
|
||||
| Target | `~/.config/ghostty/` |
|
||||
| Install | `ghostty` in `packages.arch.pacman` |
|
||||
|
||||
`~/.config/ghostty` is a `git-repo` external checkout, not chezmoi-managed contents; edit it in place and commit and push from there.
|
||||
|
||||
`config` holds every setting, ending in `theme = noctalia`.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `font-family` / `font-size` | `IoskeleyMonoTerm Nerd Font Mono` / `11` |
|
||||
| `adjust-cell-height` | `20%` (line height) |
|
||||
| `background-opacity` / `background-blur` | `0.9` / `true` |
|
||||
| `window-decoration` | `false`; `gtk-titlebar = false` |
|
||||
| `window-padding-x/y` | `0` |
|
||||
| `cursor-style` | `underline`, blinking |
|
||||
| `scrollback-limit` | `3023` (tmux keeps the real scrollback: `history-limit 50000`) |
|
||||
| `mouse-hide-while-typing` | `true` |
|
||||
| `copy-on-select` | `false` |
|
||||
| `confirm-close-surface` | `false` |
|
||||
| `link-url` / `link-previews` | `true` / `true`. Under tmux (`mouse on`) tmux receives the click, so open links with `ctrl+shift+click`; tmux opens OSC 8 links on a plain `ctrl+click` |
|
||||
| `app-notifications` | `no-clipboard-copy,no-config-reload` |
|
||||
| `quit-after-last-window-closed` | `false`, delay `5m` |
|
||||
| `command` | `shell:$HOME/.local/bin/ghostty-tmux` (see [Sessions](#sessions)) |
|
||||
| `shell-integration` | `detect`, features `cursor,sudo,title,no-cursor` |
|
||||
| `gtk-single-instance` | `true` |
|
||||
|
||||
## Sessions
|
||||
|
||||
Every Ghostty window gets its own tmux session; no two windows mirror the same one. `~/.local/bin/ghostty-tmux` (source `dot_local/bin/executable_ghostty-tmux`) runs for each new window:
|
||||
|
||||
1. If a detached session exists (left by a closed window, or restored by continuum after a reboot), it attaches to the oldest one, by session id. Background task sessions from `pj-palette` (`@pj_task` set, named `<session>:<task>`) are never reclaimed.
|
||||
2. Otherwise it starts a new session.
|
||||
|
||||
Before starting tmux, the script puts mise's shims on `PATH`. A server started from a desktop-launched Ghostty therefore finds the tools tmux plugins shell out to: bun for the palette, fzf, and zoxide.
|
||||
|
||||
The window title is the tmux session name (`set-titles-string '#S'`), so the compositor's window groups list one entry per session. tmux's tab bar keeps its own fixed cyberdream colors, so it no longer matches Ghostty's Noctalia background.
|
||||
|
||||
Closing a window only detaches its session; the shells keep running in the tmux server. In tmux, `detach-on-destroy no-detached` means killing a session moves the window to a *detached* session, or closes the window if there is none, so it never lands on another window's session. Across reboots, tmux-continuum saves every 10 minutes. The first Ghostty window starts the server, and that triggers continuum's restore. See [tmux](tmux.md#persistence).
|
||||
|
||||
## Keybindings
|
||||
|
||||
`keybind = clear` comes first and drops every Ghostty default: tabs, splits, `alt+N` goto_tab, and `ctrl+alt+arrow` goto_split. Those chords then reach tmux, which binds `alt+1…9` and `ctrl+alt+arrows` itself. `ctrl+h/j/k/l` also pass straight through to tmux's vim-aware pane navigation, which hands off to and from nvim's splits (see [tmux](tmux.md#keybindings)). Each `ctrl+shift` chord sends tmux's prefix, `ctrl+b` (`\x02`), followed by a key.
|
||||
|
||||
| Key | Ghostty action |
|
||||
| --- | --- |
|
||||
| `ctrl+shift+c` / `ctrl+shift+v` | Copy / paste |
|
||||
| `ctrl+plus` / `ctrl+minus` / `ctrl+zero` | Font size +1 / -1 / reset |
|
||||
| `ctrl+shift+.` | Reload Ghostty config |
|
||||
| `shift+enter` | Sends `ESC[13;2u` (CSI-u Shift+Enter), which tmux passes through to the pane, so coding agents such as omp and Claude Code insert a newline instead of submitting. Not `\n`: that is `ctrl+j`, tmux's pane-down key |
|
||||
| `ctrl+space`, `ctrl+backspace` | Sends NUL (tmux `C-Space`): opens the [tmux command palette](tmux.md#command-palette) |
|
||||
|
||||
| Key | Sends | tmux action |
|
||||
| --- | --- | --- |
|
||||
| `ctrl+shift+t` | `prefix c` | New window (tab) in pane cwd |
|
||||
| `ctrl+shift+p` / `ctrl+shift+space` | `prefix t` | Project Tasks palette (pj: install, dev, sync, make, every project task) |
|
||||
| `ctrl+shift+f` | `prefix y` | Yazi in a new window, pane cwd |
|
||||
| `ctrl+shift+e` | `prefix e` | Neovim in a new window, pane cwd |
|
||||
| `ctrl+shift+g` | `prefix g` | Lazygit in a 90% popup, pane cwd |
|
||||
| `ctrl+shift+a` | `prefix a` | Toggle omp in a background-session popup (`<session>:omp`), pane cwd |
|
||||
| `ctrl+shift+w` | `prefix k` | Kill window (close tab) |
|
||||
| `ctrl+shift+r` | `prefix r` | Rename window |
|
||||
| `ctrl+tab` / `ctrl+page_down` / `ctrl+shift+right` / `ctrl+shift+k` | `prefix n` | Next window |
|
||||
| `ctrl+shift+tab` / `ctrl+page_up` / `ctrl+shift+left` / `ctrl+shift+h` | `prefix p` | Previous window |
|
||||
| `ctrl+shift+l` | `prefix v` | Split right |
|
||||
| `ctrl+shift+j` | `prefix h` | Split below |
|
||||
| `ctrl+shift+o` / `super+enter` | `prefix o` | Sessions palette: switch session or window, or create one through zoxide |
|
||||
| `ctrl+shift+x` | `prefix x` | Kill pane |
|
||||
| `ctrl+shift+enter` / `ctrl+shift+z` | `prefix z` | Zoom pane |
|
||||
| `ctrl+shift+page_up` | `prefix [` | Copy mode |
|
||||
| `ctrl+shift+/` | `prefix ?` | Keybinding help popup |
|
||||
| `ctrl+shift+alt+,` | `prefix q` | Reload tmux config |
|
||||
| `ctrl+shift+n` | `prefix C` | New session |
|
||||
| `ctrl+shift+s` | `prefix s` | Session/window tree |
|
||||
| `ctrl+shift+q` | `prefix d` | Detach (closes the Ghostty window; the session lives on) |
|
||||
|
||||
## Themes
|
||||
|
||||
| Theme | Notes |
|
||||
| --- | --- |
|
||||
| `noctalia` | Active. Written by Noctalia's built-in `ghostty` template (enabled in `[theme.templates] builtin_ids` of `~/.config/noctalia/config.toml`) from the active shell palette, currently the custom `CherryBlossom` palette: background `#222120`, foreground `#dcc1b7`, salmon cursor `#cf716f`. Noctalia rewrites it and reloads Ghostty on every theme change; the repo's `.gitignore` excludes it. |
|
||||
| `cherry-blossom` | Static copy of the `CherryBlossom` palette (same colors as `noctalia`), for when Noctalia is not running or its template is disabled. Not selected by `config`; set `theme = cherry-blossom` to use it. |
|
||||
| `cyberdream` | Upstream `extras/ghostty/cyberdream` from [cyberdream.nvim](https://github.com/scottmckendry/cyberdream.nvim), the palette shared with tmux, nvim, yazi, and television. |
|
||||
| `ii-auto` | Auto-generated by the ii wallpaper theming system; not meant to be edited. |
|
||||
| `koda-dark` / `koda-light` | Koda palettes with `bold-is-bright = true`. |
|
||||
@@ -0,0 +1,90 @@
|
||||
# herdr
|
||||
|
||||
herdr, an agent-aware terminal multiplexer, with its keys, theme, plugins and Claude Code hook.
|
||||
|
||||
herdr is a terminal multiplexer with workspaces, tabs and panes, a sidebar, a prefix key (`ctrl+b`), and a plugin system. It tracks coding agents running in its panes: agent-state colors, next/previous agent keys, and an integration that reports Claude Code sessions to it.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/herdr](https://github.com/prdlk/herdr) via `.chezmoiexternals/herdr.toml` (`~/.config/herdr/`); `dot_config/herdr-nvim/config.toml` |
|
||||
| Target | `~/.config/herdr/`, `~/.config/herdr-nvim/config.toml` |
|
||||
| Install | Not in `packages.yaml`; updated by topgrade (`herdr update`) |
|
||||
| Launch | kitty `super+a` (see [kitty](kitty.md)) |
|
||||
|
||||
## config.toml
|
||||
|
||||
`onboarding = false`.
|
||||
|
||||
### UI and theme
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| `ui.pane_borders`, `ui.pane_outer_borders` | `false` (panes separated by gaps only) |
|
||||
| `ui.prompt_new_tab_name` | `false` |
|
||||
| `ui.accent` | `#5ea1ff` |
|
||||
| `ui.status_indicators` | `symbols` |
|
||||
| `ui.toast.delivery` | `system` |
|
||||
| `theme.name` | `terminal`, `auto_switch = false` |
|
||||
| `theme.custom.panel_bg` | `reset` (keeps kitty's translucency and compositor blur) |
|
||||
| `sidebar_bg` / `active_row_bg` / `selection_bg` | `#16181a` / `#1e2124` / `#3c4048` |
|
||||
| `accent` / `surface_dim` | `#243348` / `#5ea1ff` (active tab plate matches kitty's tab bar) |
|
||||
| Agent-state colors | green `#5eff6c`, yellow `#f1ff5e`, red `#ff6e5e`, peach `#ffbd5e`, mauve `#bd5eff` |
|
||||
| `experimental.kitty_graphics` | `true`; `pane_history = false` |
|
||||
| `switch_ascii_input_source_in_prefix` | `true` |
|
||||
|
||||
These are cyberdream colors.
|
||||
|
||||
### Keys
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `prefix %` | Split vertical |
|
||||
| `prefix "` | Split horizontal |
|
||||
| `prefix d` | Detach |
|
||||
| `prefix s` | Workspace picker |
|
||||
| `prefix shift+s` | Settings |
|
||||
| `k` / `j` | Move up / down in the workspace picker (pane up/down in navigate mode unbound) |
|
||||
| `prefix .` / `prefix ,` | Next / previous agent |
|
||||
| `prefix j` / `prefix k` | Next / previous workspace |
|
||||
| `prefix up/down/left/right` | Focus pane |
|
||||
| `ctrl+h/j/k/l` | `vim-herdr-navigation` left/down/up/right |
|
||||
| `prefix e` | `chmarax.herdr-nvim.toggle` (nvim sidebar) |
|
||||
| `prefix o` | `chmarax.herdr-nvim.pick-file` (open file from agent output) |
|
||||
| `prefix t` | `switchboard.menu` |
|
||||
| `prefix shift+p` | `switchboard.commands` |
|
||||
| `prefix shift+l` | `switchboard.git` |
|
||||
| `prefix shift+k` | `switchboard.projects` |
|
||||
| `prefix y` | Open `official.browser` plugin in a right split |
|
||||
| `prefix shift+o` | Open `official.browser` as an overlay |
|
||||
|
||||
## Plugins
|
||||
|
||||
`plugins.json` is herdr's plugin registry. `.plugins.lock` is committed empty. `plugins/config/official.browser/` and `plugins/config/vim-herdr-navigation/` hold placeholder `.keep` files for per-plugin config. `plugins/github/`, where herdr installs plugin checkouts, is gitignored in the repo.
|
||||
|
||||
| Plugin | Description (from its manifest) |
|
||||
| --- | --- |
|
||||
| `chmarax.herdr-nvim` | Full-height nvim sidebar with agent annotations |
|
||||
| `herdr-automatic-rename` | Names tabs after the foreground program, adds 1–9 jump numbers |
|
||||
| `official.browser` | Open and inspect real browser pages from herdr |
|
||||
| `oh-my-opencode-slim` | OpenCode agent orchestration with a herdr adapter |
|
||||
| `persiyanov.reviewr` | Review agent-written diffs beside the chat |
|
||||
| `rjyo.window-title-sync` | Syncs the outer terminal title to the focused workspace, tab and agent |
|
||||
| `switchboard` | Command palette: projects, commands, ports, git, clone, settings |
|
||||
| `vim-herdr-navigation` | Vim-aware `ctrl+h/j/k/l` pane movement |
|
||||
|
||||
[fish](../shell/fish.md) sources `herdr-automatic-rename`'s `shell/hook.fish` if present.
|
||||
|
||||
## herdr-nvim
|
||||
|
||||
**~/.config/herdr-nvim/config.toml**
|
||||
|
||||
```toml
|
||||
[sidebar]
|
||||
position = "left"
|
||||
```
|
||||
|
||||
The nvim sidebar opens to the left of existing panes (default is right).
|
||||
|
||||
## herdr/kitty.conf
|
||||
|
||||
A legacy kitty config kept in the herdr folder. It runs `tmux new-session -A -s main`, listens on `unix:@mykitty`, includes `current-theme.conf`, maps `super`/`alt` keys to tmux prefix bytes, and has a copy of an older `tmux.conf` appended. The active herdr host is `~/.config/kitty/herdr.conf`.
|
||||
@@ -0,0 +1,221 @@
|
||||
# kitty
|
||||
|
||||
kitty configuration, host variants, custom kittens and the Python tab bar.
|
||||
|
||||
GPU terminal emulator and the primary terminal on every role.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/kitty](https://github.com/prdlk/kitty) via `.chezmoiexternals/kitty.toml` |
|
||||
| Target | `~/.config/kitty/` |
|
||||
| Install | `kitty` in `.chezmoidata/packages.yaml` (pacman), plus `ttf-victor-mono-nerd` for the font |
|
||||
|
||||
```text
|
||||
- ~/.config/kitty/
|
||||
- kitty.conf
|
||||
- tmux.conf
|
||||
- herdr.conf
|
||||
- goliath.conf
|
||||
- ssh.conf
|
||||
- current-theme.conf
|
||||
- tab_bar.py
|
||||
- pass_keys.py
|
||||
- day_session.py
|
||||
- navigate_kitty.py
|
||||
- select_tab.py
|
||||
- switchtab.py
|
||||
- window_titler.py
|
||||
- themes/
|
||||
- cyberdream.conf
|
||||
- koda-dark.conf
|
||||
- koda-light.conf
|
||||
```
|
||||
|
||||
`~/.config/kitty` is a `git-repo` external checkout, not chezmoi-managed contents; edit it in place and commit and push from there. The repo's `.gitignore` excludes `__pycache__/` next to `tab_bar.py`.
|
||||
|
||||
## Theme and colors
|
||||
|
||||
Every config ends with:
|
||||
|
||||
**~/.config/kitty/kitty.conf**
|
||||
|
||||
```conf
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
```
|
||||
|
||||
The included file is written by a theme manager that this repo does not install, and kitty ignores an include whose file is missing. It would be included last so it wins. `current-theme.conf` is a symlink committed in the repo, pointing at `themes/cyberdream.conf`. Only the legacy `~/.config/herdr/kitty.conf` ([prdlk/herdr](https://github.com/prdlk/herdr)) includes `current-theme.conf`; the main config does not. `themes/` holds `cyberdream`, `koda-dark`, and `koda-light` color files.
|
||||
|
||||
## kitty.conf
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `font_family` | `VictorMono Nerd Font`, then overridden in the `BEGIN_KITTY_FONTS` block to `family="VictorMono Nerd Font Mono"`; bold/italic `auto` |
|
||||
| `window_padding_width` | `8` |
|
||||
| `hide_window_decorations` | `yes` |
|
||||
| `background_opacity` | `0.9` |
|
||||
| `cursor_trail` | `10`, `cursor_trail_start_threshold 0`, `cursor_trail_decay 0.01 0.05` |
|
||||
| `cursor_blink_interval` | `0` (set to 1, then overridden) |
|
||||
| `copy_on_select` | `auto` |
|
||||
| `strip_trailing_spaces` | `smart` |
|
||||
| `confirm_os_window_close` | `0` |
|
||||
| `allow_remote_control` | `socket-only` |
|
||||
| `listen_on` | `unix:@kitty-{kitty_pid}` |
|
||||
| `enabled_layouts` | `splits:split_axis=vertical,stack` |
|
||||
| Tab bar | `powerline`, `slanted`, centered, top edge, always shown (`tab_bar_min_tabs 1`, so the stack-layout indicator is visible with one tab), `tab_title_template "{custom}"` |
|
||||
|
||||
> [!NOTE]
|
||||
> Transparency uses kitty's own alpha, so text stays opaque. This alpha is also what lets the compositor blur the window: Umbriel's window rules enable blur for every window (see [Umbriel](../desktop/umbriel.md#window-rules)).
|
||||
|
||||
The socket also exports `KITTY_LISTEN_ON` into each pane. nvim reads it to know kitty owns the panes and hands `ctrl+h/j/k/l` back to kitty at an edge.
|
||||
|
||||
### Launch aliases
|
||||
|
||||
| Alias | Expands to |
|
||||
| --- | --- |
|
||||
| `launch_mn` | `launch --cwd=current --type=overlay-main` |
|
||||
| `launch_os` | `launch --cwd=current --type=os-window` |
|
||||
| `launch_ov` | `launch --cwd=current --type=overlay` |
|
||||
| `launch_tb` | `launch --cwd=current --type=tab` |
|
||||
| `launch_hs` | `launch --cwd=current --location=hsplit --bias=30` |
|
||||
| `launch_vs` | `launch --cwd=current --location=vsplit --bias=40` |
|
||||
| `launch_vs50` | `launch --cwd=current --location=vsplit --bias=50` |
|
||||
|
||||
### Keybindings
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `super+n` | Vertical split 50/50 (`launch_vs50`) |
|
||||
| `super+z` | Toggle `stack` layout (zoom); the tab title gains a layers icon and pane count (`nf-md-layers 3`) while other panes are hidden |
|
||||
| `ctrl+shift+t`, `super+enter` | New tab in current cwd |
|
||||
| `ctrl+space`, `ctrl+shift+j` | Horizontal split, 30% (`launch_hs`) |
|
||||
| `ctrl+enter`, `ctrl+shift+l` | Vertical split, 40% (`launch_vs`) |
|
||||
| `ctrl+shift+space` | New OS window |
|
||||
| `ctrl+shift+h` / `ctrl+shift+k` | Previous / next tab |
|
||||
| `ctrl+shift+g` | `lazygit` in an overlay |
|
||||
| `ctrl+shift+f` | `yazi` in a new tab |
|
||||
| `ctrl+shift+e` | `nvim` in a 50/50 vertical split |
|
||||
| `ctrl+shift+p` | `atuin-pick` in a horizontal split |
|
||||
| `super+t` | Spawn a separate kitty (`--class=kitty-tmux`, `--config ~/.config/kitty/tmux.conf`) |
|
||||
| `super+a` | Spawn a separate kitty (`--class=kitty-herdr`, `--config ~/.config/kitty/herdr.conf`) |
|
||||
| `ctrl+shift+c` / `ctrl+shift+v` | Copy / paste clipboard |
|
||||
| `alt+q w e r t y u i o` | Go to tab 1–9 |
|
||||
| `ctrl+h/j/k/l` | `pass_keys.py`: move to neighbor window left/bottom/top/right, or pass the key through |
|
||||
| `super+left` / `super+right` | Resize window narrower / wider |
|
||||
| `super+up` / `super+down` | Resize window taller / shorter (by 3) |
|
||||
|
||||
The tmux and herdr hosts are separate kitty processes because `launch` cannot take `--config`.
|
||||
|
||||
### Daily sessions
|
||||
|
||||
`day_session.py` keeps one kitty session file per day, grouped by week:
|
||||
|
||||
```text
|
||||
~/.local/share/kitty-sessions/<monday YYYY-MM-DD>/<day YYYY-MM-DD>.kitty-session
|
||||
~/.local/share/kitty-sessions/<monday YYYY-MM-DD>/<day YYYY-MM-DD>.scrollback/<window key>.ansi
|
||||
```
|
||||
|
||||
| Directive | Role |
|
||||
| --- | --- |
|
||||
| `geninclude day_session.py` | Creates today's (empty) session file and emits `startup_session <path>` whenever no running kitty owns today's session. Ownership is an `flock` on `$XDG_RUNTIME_DIR/kitty-day-<day>.kitty-session.lock`, held until that kitty exits, so closing kitty and launching it again always reopens the day, even if other kitties (or yesterday's) are still running. A second concurrent instance starts plain instead of duplicating today's tabs. A kitty started with `--session` for another day never takes the lock. |
|
||||
| `watcher day_session.py` | About 1 s after a window opens, closes, resizes or splits, changes focus or finishes a command (catches `cd`), and every 30 s for output from long-running programs, rewrites the session this instance started with. Title changes are ignored so spinners don't trigger saves. Skips the save once the session has no windows, so closing kitty keeps the last good state. |
|
||||
|
||||
Earlier days open with the [`kitty-days`](../tools/television.md#kitty-days) tv cable (`tv kitty-days`), each in its own kitty (`kitty --session <file>`). A past day is a read-only snapshot: nothing in that kitty saves it back. Today's file opened that way, while no kitty owns it, becomes the live day again.
|
||||
|
||||
Before shutdown or reboot, the `kitty-day-save.service` user unit runs [`kitty-day-save`](../shell/scripts.md#kitty-day-save) under a logind delay inhibitor. On `PrepareForShutdown(true)` it sends `kitten day_session.py flush` to every `@kitty-<pid>` socket: the owning kitty saves at once (all windows are still alive), then skips saves for 60 s so windows dying during the shutdown can't shrink the file. [`30-user-services`](../chezmoi/scripts.md#30-user-services) enables the unit.
|
||||
|
||||
What a restored window gets back:
|
||||
|
||||
- **Scrollback**: each window carries a `day_scrollback=<key>` user var (kept by kitty's `launch --var`); its history is dumped as ANSI to `<key>.ansi`, minus the idle prompt, and dumps of closed windows are pruned. On restore, the shell runs `cat <key>.ansi` through kitty's shell-integration startup command, so it needs shell integration (fish here). A dump is only written after the shell has drawn a prompt, so a restored window can't overwrite its history before replaying it.
|
||||
- **Running program**: saved with `--use-foreground-process` and re-run after the `cat`, so the shell is still there when it exits (`nvim`, `sleep 700`, dev servers). Commands still at shell init when saved (mise/direnv hooks) are ignored. A command is never replayed if any of its words is `rm`, `rmdir`, `mv`, `dd`, `shred`, `truncate`, `mkfs*`, `wipefs`, `fdisk`, `parted`, `sudo`, `doas`, `su`, `run0`, `pkexec`, `kill`, `pkill`, `killall`, `reboot`, `shutdown` or `poweroff` (`NO_REPLAY`).
|
||||
- Programs launched as the window's own command (`ctrl+shift+e` nvim, `ctrl+shift+g` lazygit) were already restored by kitty; they have no shell, so no scrollback.
|
||||
|
||||
Only windows that belong to the session are saved: `new_tab_with_cwd` and the `--cwd=current` launch aliases join it; `new_os_window` (`ctrl+shift+space`) does not.
|
||||
|
||||
## Host configs
|
||||
|
||||
### tmux.conf — kitty as a tmux host
|
||||
|
||||
Launched by `super+t`. kitty stays a dumb terminal; tmux owns panes, windows and sessions. `listen_on unix:@kitty-tmux`, `tab_bar_style hidden`, `shell kitty-tmux` (a launcher script), and `clear_all_shortcuts yes`, so only the bindings below exist. Prefix byte `\x02` is `ctrl+b`; see [tmux](tmux.md) for what each prefix key does.
|
||||
|
||||
| Key | Sends / does |
|
||||
| --- | --- |
|
||||
| `ctrl+shift+c` / `ctrl+shift+v` | Copy / paste |
|
||||
| `ctrl+plus` / `ctrl+minus` / `ctrl+0` | Font size +1 / -1 / reset |
|
||||
| `ctrl+shift+alt+,` | Reload kitty config |
|
||||
| `ctrl+shift+a` | Spawn a kitty herdr host |
|
||||
| `super+t`, `super+enter`, `ctrl+shift+t` | `prefix t` (new window) |
|
||||
| `ctrl+shift+h` / `ctrl+shift+k` | `prefix H` / `prefix L` (previous / next window) |
|
||||
| `alt+q w e r t y u i o` | `prefix 1`–`9` |
|
||||
| `ctrl+shift+j` | `prefix "` (split below) |
|
||||
| `ctrl+shift+l`, `ctrl+enter` | `prefix %` (split right) |
|
||||
| `ctrl+shift+f` | `prefix z` (zoom) |
|
||||
| `ctrl+shift+w`, `super+w` | `prefix x` (kill pane) |
|
||||
| `super+left/right/up/down` | `prefix` + `ctrl+arrow` (resize) |
|
||||
| `ctrl+shift+g` | `tmux display-popup` 90%×90% running `lazygit` in pane cwd |
|
||||
| `ctrl+shift+e` | `tmux new-window` running `nvim` in pane cwd |
|
||||
| `ctrl+shift+p` | `tmux split-window -v -l 25%` running `atuin-pick` |
|
||||
| `super+n`, `ctrl+shift+space` | New OS window |
|
||||
| `super+d` | `prefix d` (detach) |
|
||||
|
||||
### herdr.conf — kitty as a herdr host
|
||||
|
||||
Launched by `super+a`. Class `kitty-herdr`, `tab_bar_style hidden` (herdr draws its own sidebar and tab strip), `shell herdr` (launches or attaches the persistent session; closing the window detaches). Base appearance matches `kitty.conf`, without a `listen_on`. An uppercase letter after `\x02` is the shift variant. See [herdr](herdr.md).
|
||||
|
||||
| Key | Sends / does |
|
||||
| --- | --- |
|
||||
| `super+c` / `super+v` | Copy / paste |
|
||||
| `ctrl+plus` / `ctrl+minus` / `ctrl+0` | Font size +1 / -1 / reset |
|
||||
| `ctrl+shift+alt+,` | Reload kitty config |
|
||||
| `ctrl+shift+n` | `prefix N` |
|
||||
| `super+n` | `prefix G` |
|
||||
| `super+t`, `ctrl+shift+t` | `prefix c` |
|
||||
| `ctrl+space` | `prefix t` (Switchboard menu) |
|
||||
| `super+z` | `prefix z` |
|
||||
| `ctrl+tab` / `ctrl+shift+tab` | `prefix .` / `prefix ,` (next / previous agent) |
|
||||
| `ctrl+shift+f` | `prefix K` (Switchboard projects) |
|
||||
| `ctrl+shift+g` | `prefix L` (Switchboard git) |
|
||||
| `ctrl+shift+e` | `prefix e` (herdr-nvim sidebar toggle) |
|
||||
| `ctrl+shift+o` | `prefix o` (herdr-nvim pick file) |
|
||||
| `ctrl+shift+h` / `ctrl+shift+l` | `prefix p` / `prefix n` |
|
||||
| `ctrl+shift+j` / `ctrl+shift+k` | `prefix j` / `prefix k` (next / previous workspace) |
|
||||
| `ctrl+shift+w` | `prefix W` |
|
||||
| `ctrl+shift+r` | `prefix T` |
|
||||
| `ctrl+shift+,` | `prefix S` (herdr settings) |
|
||||
| `ctrl+shift+q` | `prefix x` |
|
||||
| `ctrl+shift+p` | `prefix P` (Switchboard commands) |
|
||||
|
||||
### goliath.conf — remote tmux over mosh
|
||||
|
||||
Opens straight into the tmux session `main` on host `goliath` via `mosh goliath -- tmux new-session -A -s main`. `font_size 9.0`, `listen_on unix:@kitty-goliath`, `env MOSH_TITLE_NOPREFIX=1` (drops mosh's `[mosh] ` title prefix), `tab_bar_style hidden`.
|
||||
|
||||
> [!WARNING]
|
||||
> mosh bootstraps ssh with `-S none`, so non-interactive key auth to goliath must work. It needs mosh on both ends and UDP 60000–61000 open. mosh sets `TERM=xterm-256color`, so kitty-only capabilities such as undercurl are unavailable inside.
|
||||
|
||||
| Key | Sends |
|
||||
| --- | --- |
|
||||
| `super+w` | `prefix x` |
|
||||
| `super+t` | `prefix %` |
|
||||
| `super+n` | `prefix "` |
|
||||
| `super+enter`, `alt+n` | `prefix t` |
|
||||
| `alt+h` / `alt+l` | `prefix H` / `prefix L` |
|
||||
| `alt+q w e r t y u i o` | `prefix 1`–`9` (`alt+t` is bound twice; the later `prefix 5` wins) |
|
||||
| `super+left/right/up/down` | `prefix` + `ctrl+arrow` |
|
||||
| `super+d` | `prefix d` |
|
||||
|
||||
### ssh.conf
|
||||
|
||||
Kitten `ssh` config: for `hostname goliath`, `share_connections yes`.
|
||||
|
||||
## Kittens and Python
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `tab_bar.py` | Provides `draw_title` for `{custom}` in the tab template. Title is `<index> <icon> <label>`: the label is the git repo root name of the tab's cwd (walks up for `.git`), or the file nvim is editing (parsed from nvim's title). Icon comes from the foreground process cmdline (editors, AI agents like claude/omp/opencode/codex, git tools, docker, herdr, tmux, yazi, btop, atuin, ssh, chezmoi, …) with a folder fallback. Labels are elided at 28 chars. In the `stack` layout with more than one pane, ` <nf-md-layers> <panes>` is appended; kitty repaints the tab bar on every layout change. No subprocesses or timers. |
|
||||
| `pass_keys.py` | Bound to `ctrl+h/j/k/l`. If the window's foreground process matches `vim`, `nvim`, `fzf`, `tv`, `tmux` or `herdr`, the key is forwarded; otherwise kitty moves to the neighboring window. |
|
||||
| `navigate_kitty.py` | No-UI kitten that calls `neighboring_window` with a direction argument; the counterpart nvim can invoke. Not mapped in `kitty.conf`. |
|
||||
| `select_tab.py` | Full-screen fuzzy tab picker using `kitty @ ls` data; shows cwds zsh-style. Keys: Up/Down/PgUp/PgDn scroll, Enter selects, typing filters, `ctrl+c`/Esc quits. Not mapped in `kitty.conf`. |
|
||||
| `switchtab.py` | Focuses the tab whose `vt` user var matches the argument, or launches one with that var and reorders tabs by `vt`. Not mapped. |
|
||||
| `window_titler.py` | Watcher that appends `[<vt> <pty>]` to window titles. Not referenced in `kitty.conf`. |
|
||||
| `day_session.py` | Daily sessions: `geninclude` script, global watcher and the `flush` no-UI kitten in one file. See [Daily sessions](#daily-sessions). |
|
||||
|
||||
Related: the [tmux](tmux.md) `is_vim` check and herdr's `vim-herdr-navigation` plugin complete the `ctrl+h/j/k/l` chain. Umbriel window rules match the `kitty`, `kitty-tmux` and `kitty-herdr` app-ids ([Umbriel](../desktop/umbriel.md#window-rules)).
|
||||
@@ -0,0 +1,114 @@
|
||||
# tmux
|
||||
|
||||
tmux prefix, bindings, kitty-style window tabs with Nerd Font icons, and resurrect/continuum persistence.
|
||||
|
||||
Terminal multiplexer. [Ghostty](ghostty.md) runs nothing but tmux, and Ghostty's `ctrl+shift` chords are tmux bindings. tmux is also used through the kitty tmux host and remotely on goliath.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/tmux](https://github.com/prdlk/tmux) via `.chezmoiexternals/tmux.toml`: `tmux.conf`, `tmux-nerd-font-window-name.yml`, `tab-label.sh` |
|
||||
| Target | `~/.config/tmux/` |
|
||||
| Install | `tmux` in `.chezmoidata/packages.yaml` (pacman) |
|
||||
|
||||
`~/.config/tmux` is a `git-repo` external checkout, not chezmoi-managed contents; edit it in place and commit and push from there. tpm is a separate `git-repo` external in `.chezmoiexternal.toml`, cloned to `~/.config/tmux/plugins/tpm` inside that checkout when missing. `tmux.conf` ends with `run '~/.config/tmux/plugins/tpm/tpm'`. Plugins installed by tpm (`prefix I`) land next to it; the tmux repo's `.gitignore` excludes `plugins/` and chezmoi ignores them. [prdlk/tmux-palette](https://github.com/prdlk/tmux-palette) (`.chezmoiexternals/tmux-palette.toml`, → `~/.config/tmux-palette/`) holds the [palette](#command-palette) config.
|
||||
|
||||
## Options
|
||||
|
||||
The prefix is `ctrl+b` (the byte Ghostty's `ctrl+shift` chords send). `ctrl+space` is not a prefix: it opens the [command palette](#command-palette).
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `default-terminal` | `tmux-256color`; `RGB` override for every terminal; `clipboard` and `hyperlinks` (OSC 8) features; `extkeys` for xterm-kitty. Features apply after `tmux kill-server` |
|
||||
| `extended-keys` | `on`, format `csi-u` |
|
||||
| `base-index` / `pane-base-index` | `1` / `1`; `renumber-windows on` |
|
||||
| `status-position` | `top`, `status-interval 5` |
|
||||
| `allow-passthrough` / `set-clipboard` / `mouse` / `focus-events` | `on` |
|
||||
| `update-environment` | `TERM` and `TERM_PROGRAM` appended, so programs see the outer terminal (Ghostty/kitty) |
|
||||
| `mode-keys` | `vi` |
|
||||
| `set-titles` | `on`, `#S`: the terminal window title is the session name, so compositor window groups list sessions (one per Ghostty window) instead of `host:window` |
|
||||
| `automatic-rename-format` | `#{window_icon} <label>`: Nerd Font icon, then the dynamic label described under [Window tabs](#window-tabs) |
|
||||
| `history-limit` | `50000` |
|
||||
| `detach-on-destroy` | `no-detached` (killing a session switches only to a detached session, else detaches, so a Ghostty window never lands on another window's session) |
|
||||
|
||||
These settings are what [yazi](../tools/yazi.md) needs inside tmux. `allow-passthrough` carries image previews through to the terminal, `set-clipboard` copies paths via OSC 52, and the two environment variables let yazi detect the outer terminal's graphics support. Nothing binds `M-[`: tmux would read the `ESC [` that starts yazi's terminal-query replies as that key and break yazi's capability detection.
|
||||
|
||||
## Keybindings
|
||||
|
||||
`prefix ?` opens all of them in a popup: `tmux list-keys -N` (the config prefers a keybindings helper script when one is on `PATH`).
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| `prefix q` | Reload config |
|
||||
| `prefix /` | Copy mode, then search upward |
|
||||
| `prefix o` | Sessions palette ([`tmux-sessions`](../shell/scripts.md#tmux-sessions)): fuzzy-find the other sessions, every window of every session, zoxide's and `~/.config`'s directories, and Rename/Kill items per session. Typed text that is not a session starts one by that name in its zoxide match |
|
||||
| `prefix t` | Project Tasks palette ([`pj-palette`](../shell/scripts.md#pj-palette)): install/dev/sync/make/compose quick actions plus every `pj` task. One-off tasks run in a full-width bottom split (25% of the window); dev servers and `docker compose up` run in a background `<session>:<tool-and-command>` session shown in a popup (`q` hides, `ctrl-c` asks `y/n` then stops), listed under Running to reopen |
|
||||
| `prefix y` / `prefix e` | Yazi / Neovim in a new window in the pane cwd (Ghostty `ctrl+shift+f` / `ctrl+shift+e`) |
|
||||
| `prefix g` | Lazygit in a 90% popup in the pane cwd (Ghostty `ctrl+shift+g`) |
|
||||
| `prefix a` | omp in a background session `<session>:omp` (pane cwd), shown in a popup docked bottom-right (35% × 60%) and listed in the [omp palette](../shell/scripts.md#pj-palette) and under Running in the tasks palette. In the popup, `ctrl-c` hides it and `ctrl-q` quits omp. Inside any background-task popup, `prefix a` hides it too, so Ghostty `ctrl+shift+a` toggles omp |
|
||||
| `prefix h` / `prefix v` | Split below / right, pane cwd |
|
||||
| `prefix x` | Kill pane |
|
||||
| `S-Enter` (no prefix) | Sends `ESC[13;2u` to the pane unchanged. With `extended-keys on`, tmux would otherwise turn Shift+Enter into plain Enter for apps that never requested extended keys, and coding agents (omp, Claude Code) would submit instead of inserting a newline |
|
||||
| `C-MouseDown1Pane` (ctrl+click) | Opens the OSC 8 hyperlink under the mouse (`#{mouse_hyperlink}`) with `xdg-open`; without one, the click goes to the pane. Plain-text URLs: `ctrl+shift+click`, handled by Ghostty |
|
||||
| `M-Enter` / `M-S-Enter` / `M-Escape` (no prefix) | Split below / split right / kill pane |
|
||||
| `C-h` / `C-j` / `C-k` / `C-l` (no prefix) | Vim-aware pane focus. In an nvim/vim/fzf pane (`@pane-is-vim`, set by smart-splits, or the pane's command), the key goes to nvim. nvim's `config.pane_nav` moves between its own splits and, at its outer edge, runs `tmux select-pane -t $TMUX_PANE`. Elsewhere tmux moves directly. Also bound in copy mode |
|
||||
| `prefix C-l` | Send `C-l` (clear screen) |
|
||||
| `C-M-arrows` / `C-M-S-arrows` (no prefix) | Focus pane / resize pane by 5 |
|
||||
| `prefix c` / `prefix k` / `prefix r` | New window in pane cwd / kill window / rename window |
|
||||
| `M-1` … `M-9` (no prefix) | Select window 1–9 |
|
||||
| `M-Left` / `M-Right` (no prefix) | Previous / next window |
|
||||
| `M-S-Left` / `M-S-Right` (no prefix) | Move window left / right |
|
||||
| `prefix C` / `prefix K` / `prefix R` | New / kill / rename session |
|
||||
| `prefix P` / `prefix N`, `M-Up` / `M-Down` | Previous / next session |
|
||||
| Copy mode `v` / `y` | Begin selection / copy and exit |
|
||||
|
||||
## Window tabs
|
||||
|
||||
The status line copies kitty's tab bar (`tab_bar_style powerline`, `tab_powerline_style angled`). The current window is an accent-tinted plate with accent text. The other windows use `bg=default` and muted text, so the terminal's transparency shows through them, as with kitty's `tab_bar_background none`. That only works while every `window-status-*-style` stays `default`: inside a tab format, `bg=default` means that style's background, not the terminal's. `tmux.conf` therefore sets all of them explicitly, because a reload never clears a value a removed plugin left behind. As in kitty, a solid arrow leads into the current window and a thin one sits between two inactive windows. The arrow into the current window is a plain `U+E0B0` in `@tab-bg`, the window background. Ghostty draws that glyph as a built-in sprite that fills the whole cell even with `adjust-cell-height`. The session name is an accent pill on the far left (`status-justify left`); its arrow points straight into the first tab, so the pill and tabs read as one chain of arrows. The right side shows `COPY`/`PREFIX`/`ZOOM` flags and the hostname.
|
||||
|
||||
Colors are the user options `@tab-bg` (window background: the notch arrow and the session pill's text), `@tab-fg`, `@tab-active-bg`, and `@tab-active-fg`. They are a fixed CherryBlossom palette (the Noctalia custom palette in [prdlk/noctalia](https://github.com/prdlk/noctalia) `palettes/CherryBlossom.json`, the same one Ghostty's `cherry-blossom` theme uses), so the bar looks identical on every machine. Active background = accent mixed 20% into background, the same derivation as the kitty tab bar: `#453130` from accent `#cf716f` over `#222120`.
|
||||
|
||||
Popups (`prefix g`, `prefix t`, `prefix ?`, tmux-palette) set `popup-style` to `bg=@tab-bg` and `popup-border-style` to `@tab-active-fg` on that background with rounded lines. The explicit background matters under Ghostty: `background-opacity` only applies to cells with the default background, so a default-bg popup shows the pane behind it through the blur.
|
||||
|
||||
### Tab labels
|
||||
|
||||
Each window reads `<index> <icon> <label>`, updating as you work, the same rule as kitty's `tab_bar.py`:
|
||||
|
||||
| Pane runs | Label | Source |
|
||||
| --- | --- | --- |
|
||||
| `nvim`/`vim`/`hx`/`helix`, editing a file | The file name | The title nvim sets (`<file> \| <org/repo>`, astrocore `custom_title`), read from `#{pane_title}` by the format itself, so a buffer switch relabels the tab immediately |
|
||||
| Anything else, or an editor with no file | Repo the cwd belongs to (walks up for `.git`); the directory name outside a repo; `~` at home | `tab-label.sh`, run as an async `#()` job. When its output differs from the current name, it re-arms `automatic-rename` so tmux relabels right after a `cd` instead of on the next keystroke |
|
||||
|
||||
Labels longer than 27 characters end in `…`. The icon comes from tmux-nerd-font-window-name.
|
||||
|
||||
## Plugins
|
||||
|
||||
| Plugin | Settings |
|
||||
| --- | --- |
|
||||
| `tmux-plugins/tpm` | Plugin manager |
|
||||
| `tmux-plugins/tmux-sensible` | Defaults |
|
||||
| `joshmedeski/tmux-nerd-font-window-name` | Fills `#{window_icon}` from `tmux-nerd-font-window-name.yml` |
|
||||
| `tmux-plugins/tmux-resurrect` | `@resurrect-capture-pane-contents on` |
|
||||
| `tmux-plugins/tmux-continuum` | `@continuum-save-interval 10`, `@continuum-restore on` |
|
||||
| `prdlk/tmux-palette-plugin` | Fork of [eduwass/tmux-palette](https://github.com/eduwass/tmux-palette) that adds a custom palette `query` item (an item for the typed text, listed after the matches). `@palette-key C-Space` (root table, no prefix). Needs [Bun](https://bun.sh); tpm runs `bun install` on first load |
|
||||
|
||||
### Command palette
|
||||
|
||||
`ctrl+space` opens tmux-palette, a fuzzy command list in a popup. Its user config lives in `~/.config/tmux-palette/` (the [prdlk/tmux-palette](https://github.com/prdlk/tmux-palette) external):
|
||||
|
||||
| File | Contents |
|
||||
| --- | --- |
|
||||
| `commands.json` | The kitty launchers ported as palette items: **Lazygit** (90% popup in the pane's cwd; kitty `ctrl+shift+g`), **Yazi** (new window; kitty `ctrl+shift+f`), **Neovim Split** (nvim in a split to the left, 60% of the pane; alias `nv`; kitty `ctrl+shift+e`), **Shell History (atuin)** (`atuin-pick` in a 25% split below; kitty `ctrl+shift+p`). Also **btop** (80% popup; aliases `top`, `htop`), **Lazydocker** (90% popup in the pane's cwd; alias `dd`), **mandible** (80% popup running [`mandible-pick`](../shell/scripts.md#mandible-pick); alias `man`), **television** (`tv channels`, the [channel picker](../tools/television.md), in a new window in the pane's cwd; alias `tv`), **gitframe** (git diff viewer in a new window in the pane's cwd; alias `gf`), **omp** (opens the omp palette; aliases `agent`, `ai`), **Project Tasks** (opens the tasks palette), **Sessions** (opens the sessions palette; aliases `sx`, `ss`), **Save Sessions**, and **Restore Sessions** (resurrect). Programs are started with `exec`, so the tab icon is the program's, not the shell's |
|
||||
| `palettes/tasks.json` | The **Project Tasks** palette (`prefix t`, Ghostty `ctrl+shift+p` / `ctrl+shift+space`), fed by [`pj-palette`](../shell/scripts.md#pj-palette): running background tasks under Running, then `bun install`, `bun run dev`, `uv sync`, `uv run python`, `make`, `go run .`, `cargo run`, `docker compose up` as Quick items when the project has them, then every task `pj` finds, grouped by tool. One-off tasks run in a full-width split along the bottom (25% of the window) in their directory; `ctrl-c` stops a running task, and once it has exited, `enter` or `ctrl-c` closes the pane. Dev servers (`dev`/`start`/`serve`/`watch`/`preview` tasks) and `docker compose up` run in a background session `<session>:<tool-and-command>` shown in a popup: `q` hides it with the task still running, `ctrl-c` asks `Stop <session>? (y/n)` and on `y` stops the task and closes it |
|
||||
| `palettes/omp.json` | The **omp** palette, fed by [`pj-palette --omp`](../shell/scripts.md#pj-palette): this tmux session's `<session>:omp` (open it, or start it when not running), every other tmux session's running omp, and a Quit item per session. Running agents have a green icon and show `open` or `hidden` |
|
||||
| `palettes/sessions.json` | The **Sessions** palette (`prefix o`, Ghostty `ctrl+shift+o`), fed by [`tmux-sessions`](../shell/scripts.md#tmux-sessions): Sessions (the last one first, attached ones green), Windows, Zoxide, Config, Rename, Kill. Its `query` item lists the typed text last; `enter` on it runs `tmux-sessions --new <text>` |
|
||||
| `hidden.json` | Hides the built-in **Switch Theme...** item, and with it the Appearance section: the palette stays on the fixed CherryBlossom `theme.json` |
|
||||
| `shortcuts.json` | Ghostty `ctrl+shift` chords shown next to the built-in items they trigger (New Window → `Ctrl+Shift+T`, …) |
|
||||
| `theme.json` | CherryBlossom: bg `#222120`, panel `#2e2c2b`, selected `#453130` (the active-tab plate), muted `#a6958f`, accent `#cf716f` |
|
||||
|
||||
### Persistence
|
||||
|
||||
resurrect saves sessions, windows, panes, cwds, pane contents, and a default set of programs (nvim, less, man, …). Keys: `prefix C-s` saves, `prefix C-r` restores. continuum saves automatically every 10 minutes. When a tmux server starts and no other tmux server is running, continuum restores the last save. In practice that means the first Ghostty window after login or a reboot.
|
||||
|
||||
## tmux-nerd-font-window-name.yml
|
||||
|
||||
`show-name: false`, because `automatic-rename-format` supplies the label. The icon sits on the left, with a fallback icon and a blank multi-pane icon. Icons are defined for `mprocs`, `mm`, `macmon`, `make`, `zsh`, `nvim`, `devbox`, `psql`, `yazi`, `lazygit`, `gh`, `tv`, `docker`, `bun`, `node`, `npm`, `pnpm`, `go`, `bunx`, `claude`, `omp`, and `gitframe`. The plugin parses the YAML with awk and does not interpret `\U` escapes, so the glyphs are literal characters.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Tools
|
||||
|
||||
CLI and TUI tools the repo installs and configures, with the settings applied to each.
|
||||
|
||||
This section covers the command-line and terminal-UI tools outside the shell, terminal, and desktop layers. Every tool here is universal: none of it is gated by machine role, so `desktop`, `laptop`, and `server` machines all get it (see [Installation](../installation.md)).
|
||||
|
||||
Packages come from `.chezmoidata/packages.yaml` and are installed by `run_onchange_before_10-packages.sh.tmpl` (pacman plus yay or paru for the AUR). See [chezmoi scripts](../chezmoi/scripts.md).
|
||||
|
||||
## Configured tools
|
||||
|
||||
| Page | Tool | Source in repo | Theme |
|
||||
| --- | --- | --- | --- |
|
||||
| [Git](git.md) | git, delta, diffnav, git-split-diffs | `dot_gitconfig.tmpl` | delta colors hand-tuned (dark) |
|
||||
| [GitHub CLI](github-cli.md) | gh, gh extensions, gh-dash | `dot_config/gh/`, `dot_config/gh-dash/` | gh-dash defaults |
|
||||
| [Neovim](neovim.md) | nvim (AstroNvim) | external repo via `.chezmoiexternal.toml` | managed in the nvim repo |
|
||||
| [Yazi](yazi.md) | yazi file manager | external repo via `.chezmoiexternals/yazi.toml` | cyberdream flavor (ii-auto flavor shipped) |
|
||||
| [Lazygit](lazygit.md) | lazygit | `dot_config/lazygit/` | cyberdream colors (ii theme file shipped) |
|
||||
| [Lazydocker](lazydocker.md) | lazydocker | `dot_config/lazydocker/config.yml` | cyberdream colors |
|
||||
| [bat](bat.md) | bat | `dot_config/bat/` | cyberdream tmTheme |
|
||||
| [Topgrade](topgrade.md) | topgrade | `dot_config/topgrade.toml` | n/a |
|
||||
| [Hunk](hunk.md) | hunk diff viewer | `dot_config/hunk/config.toml` | `auto` |
|
||||
| [drydock](drydock.md) | drydock repo fleet view | `dot_config/drydock/config.toml` | n/a |
|
||||
| [television](television.md) | tv fuzzy picker, custom cables | external repo via `.chezmoiexternals/television.toml` | `cyberdream-transparent` |
|
||||
| [LeetCode](leetcode.md) | leetcode CLI, `lc-elir` | `dot_leetcode/` | n/a |
|
||||
|
||||
- [Git](git.md): Identity from chezmoi data, diff pagers, gh credential helper.
|
||||
- [Yazi](yazi.md): Keymap, plugins, bookmarks, and the Neovim bridge.
|
||||
- [Lazygit](lazygit.md): GUI options, delta renderer, custom gh commands.
|
||||
- [GitHub CLI](github-cli.md): Aliases, extensions, and the gh-dash dashboard.
|
||||
|
||||
## Other packaged CLI tools
|
||||
|
||||
These packages are declared in `.chezmoidata/packages.yaml` but have no config file in the repo. The "Wired in" column lists where the repo actually uses them.
|
||||
|
||||
| Tool | Role | Wired in |
|
||||
| --- | --- | --- |
|
||||
| `fzf` | Fuzzy finder | `FZF_DEFAULT_OPTS` in `dot_config/fish/conf.d/10-env.fish` (rounded border, cyberdream colors: highlight `#5ef1ff`, pointer `#ffaecf`, marker `#5eff6c`). Used by fish functions `c2p`, `cp-appid`, `fab`, `gtm`, `mdp`, `lc-elir`, by `~/.local/bin` scripts (`lc-elir`), and as `fuzzy_cmd` of the yazi bunny plugin. The shell pickers (`gho`, `pj`, <kbd>ctrl-e</kbd>) use [television](television.md). |
|
||||
| `fd` | File finder | `c2p`, `cp-appid`, `extract_agents`, `mdp`, `lc-elir`, `conf.d/pnpm-filter.fish`. |
|
||||
| `ripgrep` | Content search | `cp-appid`, `gtm`, `gh-sonr-org-migrate` fish functions. |
|
||||
| `zoxide` | Frecency `cd` | `zoxide init fish` in `conf.d/40-tools.fish`; the `cd` function in fish/bash/zsh falls back to `zoxide query` when the argument is not a directory, and `ghg-clone` registers new clones. |
|
||||
| `eza` | `ls` replacement | Interactive shells alias `ls` to `eza --icons` (fish `conf.d/20-aliases.fish`, `~/.config/shell/aliases.sh`). |
|
||||
| `jq` | JSON processor | `fab`, `gtm`, `gh-sonr-org-migrate`, `pnpm-filter`. |
|
||||
| `duf` | Disk usage overview | Not referenced by any config. |
|
||||
| `btop` | Resource monitor | kitty tab bar shows an icon for it. |
|
||||
| `fastfetch` | System info | Not referenced by any config. |
|
||||
| `gum` | Shell prompts and pickers | `gdc` (confirm before `git reset --hard`), `ghn` (host and org pickers), `gtm`, `ai`, `pnpm-filter`, nvm activation messages. |
|
||||
| `glow` | Markdown renderer | Kitty tab-bar icon only. |
|
||||
| `micro` | Terminal editor | Alias `sm` = `sudo micro`. |
|
||||
| `ffmpeg`, `ffmpegthumbnailer`, `poppler`, `resvg`, `imagemagick`, `7zip`, `unzip` | Media, PDF, SVG, image, and archive backends | Grouped in the manifest as yazi preview dependencies. See [Yazi](yazi.md). |
|
||||
| `wget` | HTTP downloads | Not referenced by any config. |
|
||||
| `zsh-syntax-highlighting` | zsh highlighting | Sourced by the zsh setup. See [Shell](../shell/README.md). |
|
||||
| `ttf-victor-mono-nerd` / `font-victor-mono-nerd-font` | Nerd Font | Terminal font. See [Terminal](../terminal/README.md). |
|
||||
| `suvadu`, `mandible`, `worktrunk` | Declared under `packages.shared.cargo` | Installed by [`15-tools`](../chezmoi/scripts.md#15-tools), which installs rustup first when `cargo` is missing. `mandible` backs the tmux-palette "mandible" item via `~/.local/bin/mandible-pick`. `worktrunk` is the `wt` git-worktree CLI for parallel agent workflows. |
|
||||
|
||||
Tools with their own pages in other sections: [fish](../shell/fish.md), [starship](../shell/starship.md), and [atuin](../shell/atuin.md) under Shell; [kitty](../terminal/kitty.md) and [tmux](../terminal/tmux.md) under Terminal.
|
||||
|
||||
> [!NOTE]
|
||||
> `topgrade` comes from the AUR. See the individual pages for per-tool package details.
|
||||
@@ -0,0 +1,74 @@
|
||||
# bat
|
||||
|
||||
bat with the cyberdream tmTheme, an external PureScript syntax, and automatic cache rebuilds on apply.
|
||||
|
||||
[bat](https://github.com/sharkdp/bat) is a `cat` replacement with syntax highlighting and git gutter marks. The repo sets its theme to cyberdream, adds a PureScript grammar, and rebuilds bat's cache whenever those inputs change.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Sources | `dot_config/bat/config`, `dot_config/bat/themes/cyberdream.tmTheme` |
|
||||
| External | `.config/bat/syntaxes/sublime-purescript-syntax` in `.chezmoiexternal.toml` |
|
||||
| Targets | `~/.config/bat/config`, `~/.config/bat/themes/cyberdream.tmTheme`, `~/.config/bat/syntaxes/sublime-purescript-syntax/` |
|
||||
| Packages | `bat` (pacman) |
|
||||
|
||||
## config
|
||||
|
||||
bat's config file holds default command-line flags. One is active:
|
||||
|
||||
**~/.config/bat/config**
|
||||
|
||||
```sh
|
||||
--theme="cyberdream"
|
||||
```
|
||||
|
||||
The file also carries bat's stock commented examples (`--italic-text=always`, `--paging=never`, a mouse-enabled `--pager`, `--map-syntax`); none are enabled.
|
||||
|
||||
## Theme
|
||||
|
||||
`cyberdream.tmTheme` is a TextMate theme named "Cyberdream". Base settings:
|
||||
|
||||
| Key | Color |
|
||||
| --- | --- |
|
||||
| `background` | `#16181a` |
|
||||
| `foreground`, `caret` | `#ffffff` |
|
||||
| `lineHighlight`, `selection` | `#3c4048` |
|
||||
| `findHighlight` | `#5ef1ff` |
|
||||
| `activeGuide` | `#ffbd5e` |
|
||||
| `bracketsForeground` | `#ff5ea0` (underlined) |
|
||||
| Comments | `#7b8496` |
|
||||
|
||||
The same file is also used outside bat:
|
||||
|
||||
- The [yazi](yazi.md) cyberdream flavor points `syntect_theme` at `~/.config/bat/themes/cyberdream.tmTheme`.
|
||||
- Yazi text previews run `bat -p --color=always` through the piper plugin, so they get this theme via the config file.
|
||||
|
||||
## PureScript syntax
|
||||
|
||||
**.chezmoiexternal.toml**
|
||||
|
||||
```toml
|
||||
[".config/bat/syntaxes/sublime-purescript-syntax"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/tellnobody1/sublime-purescript-syntax.git"
|
||||
[".config/bat/syntaxes/sublime-purescript-syntax".pull]
|
||||
args = ["--ff-only"]
|
||||
```
|
||||
|
||||
chezmoi clones the Sublime grammar when it is missing. With no `refreshPeriod`, it only pulls (`--ff-only`) on `chezmoi apply --refresh-externals`. See [externals and ignore rules](../chezmoi/externals-and-ignore.md).
|
||||
|
||||
## Cache rebuilds
|
||||
|
||||
bat only sees custom themes and syntaxes after `bat cache --build`. Two scripts run it:
|
||||
|
||||
| Script | When it runs |
|
||||
| --- | --- |
|
||||
| `run_onchange_after_build-bat-cache.sh.tmpl` | Embeds SHA-256 hashes of `cyberdream.tmTheme` and `config`, so it re-runs whenever either file changes. Exits quietly if `bat` is missing. |
|
||||
| `.chezmoiscripts/run_onchange_after_40-post.sh.tmpl` | Post-apply housekeeping (also runs `fc-cache` on Linux). Runs `bat cache --build` when its own rendered content changes, which includes the first apply. |
|
||||
|
||||
> [!NOTE]
|
||||
> Neither hash covers the PureScript external. If the grammar updates, run `bat cache --build` by hand.
|
||||
|
||||
## Other consumers
|
||||
|
||||
- `lc-elir` previews candidate files with `bat --color=always --line-range=:40` inside fzf. See [LeetCode](leetcode.md).
|
||||
- The kitty tab bar shows a book icon while `bat` runs.
|
||||
@@ -0,0 +1,23 @@
|
||||
# drydock
|
||||
|
||||
drydock fleet view of uncommitted, unpushed, and unreleased work, scanning the ghq root.
|
||||
|
||||
drydock lists what is uncommitted, unpushed, and unreleased across every repo under its roots.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_config/drydock/config.toml` |
|
||||
| Target | `~/.config/drydock/config.toml` (holds live beside it in `holds.toml`, unmanaged) |
|
||||
| Packages | Not declared in `packages.yaml`; installed with cargo (`~/.cargo/bin/drydock`) |
|
||||
|
||||
## config.toml
|
||||
|
||||
**~/.config/drydock/config.toml**
|
||||
|
||||
```toml
|
||||
roots = ["~/Code"]
|
||||
```
|
||||
|
||||
Only `roots` is set; everything else stays at drydock's defaults (`drydock config show` prints the effective config). The root is the ghq root, where repos sit at `<host>/<owner>/<repo>`, within the default `max_depth = 4`. `~/Projects` would find nothing: it holds only symlinks (made by [`projects-sync`](../shell/scripts.md#projects-sync)), and drydock does not follow symlinks by default (`follow_symlinks = false`).
|
||||
|
||||
`drydock --root <path>` replaces the configured roots for one run.
|
||||
@@ -0,0 +1,114 @@
|
||||
# Git
|
||||
|
||||
Global git config with templated identity, three diff pagers, an nvim difftool, and gh/tea credential helpers.
|
||||
|
||||
Git's global config is a single chezmoi template. It sets the commit identity from chezmoi data, routes diffs through diffnav and git-split-diffs, styles delta for the tools that call it, and delegates HTTPS credentials to `gh` and `tea`.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_gitconfig.tmpl` |
|
||||
| Target | `~/.gitconfig` |
|
||||
| Type | Template (rendered, so it is written as a regular file, not a symlink) |
|
||||
| Packages | `git`, `git-delta`, `diffnav` (pacman) |
|
||||
|
||||
> [!WARNING]
|
||||
> `git-split-diffs` (the `core.pager`) is not declared in `packages.yaml`.
|
||||
|
||||
## Identity
|
||||
|
||||
**~/.gitconfig**
|
||||
|
||||
```ini
|
||||
[user]
|
||||
email = {{ .email }}
|
||||
name = {{ .name }}
|
||||
```
|
||||
|
||||
`.name` and `.email` are prompted once by `.chezmoi.toml.tmpl` on the first `chezmoi init` and stored in the local chezmoi config. No role or OS condition changes this file.
|
||||
|
||||
## Pagers
|
||||
|
||||
Three pagers are configured, each for a different path:
|
||||
|
||||
| Setting | Value | Applies to |
|
||||
| --- | --- | --- |
|
||||
| `pager.diff` | `diffnav` | `git diff`: file-tree diff viewer built on delta |
|
||||
| `core.pager` | `git-split-diffs --color`, piped into `less -+LFX` | Every other paged command (`git log -p`, `git show`, ...) |
|
||||
| `[split-diffs] min-line-width` | `40` | git-split-diffs side-by-side threshold |
|
||||
| `[split-diffs] theme-name` | `dark` | git-split-diffs theme |
|
||||
|
||||
`less -+LFX` resets the `-L`, `-F`, and `-X` flags, so less always takes over the screen and never exits early on short output.
|
||||
|
||||
### delta styling
|
||||
|
||||
The `[delta]` section is not a pager setting; delta reads it whenever it runs. Here that means diffnav and [lazygit](lazygit.md), which renders diffs with `delta --dark --paging=never`.
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `dark` | `true` |
|
||||
| `tabs` | `2` |
|
||||
| `file-style` / `file-decoration-style` | `omit` / `none` (no file header) |
|
||||
| `line-numbers-left-format` | `"{nm:>4} "` |
|
||||
| `line-numbers-right-format` | `"│ {np:>4} "` |
|
||||
| `line-numbers-left-style` | `white dim` |
|
||||
| `line-numbers-right-style` | `"#1f2335" dim` |
|
||||
| `line-numbers-zero-style`, `-plus-style`, `-minus-style` | `white dim` |
|
||||
| `wrap-left-symbol`, `wrap-right-symbol`, `wrap-right-prefix-symbol` | a single space each (wrap markers hidden) |
|
||||
| `plus-style` | `syntax "#0e250e"` |
|
||||
| `plus-emph-style` | `syntax "#103610"` |
|
||||
| `minus-style` | `syntax "#402a26"` |
|
||||
| `minus-emph-style` | `syntax "#45221c"` |
|
||||
| `hunk-label` | a Nerd Font glyph |
|
||||
| `hunk-header-line-number-style` | `"#10233A"` |
|
||||
| `hunk-header-style` | `"#868E99"` |
|
||||
| `hunk-header-file-style` | `"#868E99" dim` |
|
||||
| `hunk-header-decoration-style` | `"#163050" ol ul` |
|
||||
|
||||
Added and removed lines keep syntax highlighting (`syntax` foreground) on dark green and dark red backgrounds.
|
||||
|
||||
## Difftool
|
||||
|
||||
**~/.gitconfig**
|
||||
|
||||
```ini
|
||||
[diff]
|
||||
tool = codediff
|
||||
[difftool "codediff"]
|
||||
cmd = nvim \"$LOCAL\" \"$REMOTE\" +\"CodeDiff file $LOCAL $REMOTE\"
|
||||
```
|
||||
|
||||
`git difftool` opens both versions in [Neovim](neovim.md) and runs the `:CodeDiff` command, which comes from the external nvim config. Lazygit's `openDiffTool` binding (`<c-t>`) uses the same tool.
|
||||
|
||||
## Remotes and push
|
||||
|
||||
| Setting | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `url "git@github.com:".pushInsteadOf` | `https://github.com/` | Remotes cloned over HTTPS push over SSH; fetches stay on HTTPS. |
|
||||
| `push.autoSetupRemote` | `true` | First `git push` on a new branch sets its upstream automatically. |
|
||||
| `advice.addIgnoredFile` | `false` | Silences the hint when `git add` touches an ignored path. |
|
||||
| `beads.role` | `maintainer` | Role read by the beads (`bd`) issue tracker. |
|
||||
|
||||
## Credential helpers
|
||||
|
||||
Each host block first sets an empty `helper =`, which clears any helper inherited from system config, then adds one helper:
|
||||
|
||||
| Host | Helper | Notes |
|
||||
| --- | --- | --- |
|
||||
| `https://github.com` | `!gh auth git-credential` | Uses the token from `gh auth login`. See [GitHub CLI](github-cli.md). |
|
||||
| `https://gist.github.com` | `!gh auth git-credential` | Same as above, for gists. |
|
||||
| `https://prad.codes` | `!tea login helper` | Self-hosted Gitea; uses the login stored by the `tea` CLI. |
|
||||
|
||||
No credentials are stored in the file itself.
|
||||
|
||||
## Related helpers
|
||||
|
||||
These git-adjacent commands live in `~/.local/bin` and the shell function directories. See [shell scripts](../shell/scripts.md) for the full set.
|
||||
|
||||
| Command | What it does |
|
||||
| --- | --- |
|
||||
| `gdc` | Asks for confirmation with `gum confirm`, then runs `git reset --hard HEAD` and `git clean -fd`. |
|
||||
| `gop` | Opens a repo's web page for any forge whose layout is `https://<host>/<owner>/<repo>`; defaults to `origin`, then `upstream`. Also the <kbd>ctrl-b</kbd> action of the `ghq-repos` [television](television.md#ghq-repos) cable. |
|
||||
| `git-summary` | Aggregates every `AGENTS.md` in the current repo through `code2prompt`. |
|
||||
| `ghg` / `ghg-clone` | `ghq get` a repo and `cd` into the clone; sets up a prad.codes pull mirror (your GitHub repos) or GitHub push mirror (prad.codes repos) when none exists. |
|
||||
| `ghn` | Creates a private repo on prad.codes (via `tea`) or github.com (via `gh`), clones it with ghq, and `cd`s into it. |
|
||||
| `gho` | Picks a ghq repo or Obsidian vault with the `ghq-repos` [television](television.md#ghq-repos) cable and `cd`s into it. |
|
||||
@@ -0,0 +1,151 @@
|
||||
# GitHub CLI
|
||||
|
||||
gh config and aliases, the four gh extensions installed on apply, and the gh-dash dashboard layout.
|
||||
|
||||
`gh` is GitHub's official CLI. The repo manages its global config, installs a fixed set of extensions, and configures [gh-dash](https://github.com/dlvhdr/gh-dash), a TUI dashboard for pull requests, issues, and notifications.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Sources | `dot_config/gh/private_config.yml`, `dot_config/gh-dash/config.yml` |
|
||||
| Targets | `~/.config/gh/config.yml` (mode `0600`, from the `private_` prefix), `~/.config/gh-dash/config.yml` |
|
||||
| Packages | `github-cli` (pacman) |
|
||||
| Extensions | `packages.shared.gh_extensions` in `.chezmoidata/packages.yaml` |
|
||||
|
||||
> [!NOTE]
|
||||
> Authentication is per machine: run `gh auth login` once. Tokens live in gh's `hosts.yml` or the system keyring, which the repo does not manage.
|
||||
|
||||
## gh config
|
||||
|
||||
| Key | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `version` | `1` | Config schema version. |
|
||||
| `git_protocol` | `https` | gh clones and adds remotes over HTTPS. Pushes still go over SSH because of the `pushInsteadOf` rewrite in [Git](git.md). |
|
||||
| `editor` | empty | Falls back to the environment; fish exports `EDITOR=nvim`. |
|
||||
| `prompt` | `enabled` | Interactive prompts on. |
|
||||
| `prefer_editor_prompt` | `disabled` | Prompts inline instead of opening an editor. |
|
||||
| `pager` | empty | Falls back to the environment. |
|
||||
| `http_unix_socket` | empty | Default transport. |
|
||||
| `browser` | empty | Falls back to the environment. |
|
||||
| `color_labels` | `disabled` | Labels are not drawn in their RGB colors. |
|
||||
| `accessible_colors` | `disabled` | |
|
||||
| `accessible_prompter` | `disabled` | |
|
||||
| `spinner` | `enabled` | Animated progress spinner. |
|
||||
|
||||
### Aliases
|
||||
|
||||
| Alias | Expands to |
|
||||
| --- | --- |
|
||||
| `gh co` | `gh pr checkout` |
|
||||
|
||||
## Extensions
|
||||
|
||||
| Extension | Command | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `davidraviv/gh-clean-branches` | `gh clean-branches` | Deletes local branches that no longer have a remote counterpart. |
|
||||
| `prdlk/gh-commit` | `gh commit` | The owner's commit helper; lazygit binds it as "Smart commit with AI". |
|
||||
| `dlvhdr/gh-dash` | `gh dash` | PR, issue, and notification dashboard (configured below). |
|
||||
| `meiji163/gh-notify` | `gh notify` | Notification viewer. |
|
||||
|
||||
`.chezmoiscripts/run_onchange_after_20-gh-extensions.sh.tmpl` installs them. It is a fish script, so fish's config is sourced first and `PATH` and `GITHUB_TOKEN` match a fish session:
|
||||
|
||||
1. **Re-run trigger**: The rendered script embeds a SHA-256 of the `gh_extensions` list, so chezmoi re-runs it whenever the list changes.
|
||||
2. **Guards**: If `gh` is missing, or `gh auth status` fails, it prints a warning and exits 0. Run `gh auth login`, then `chezmoi apply` again.
|
||||
3. **Install**: Reads `gh extension list` and runs `gh extension install` only for entries not already present.
|
||||
|
||||
See [chezmoi scripts](../chezmoi/scripts.md) for the full script order.
|
||||
|
||||
## gh-dash
|
||||
|
||||
Launched with `gh dash`, or <kbd>Ctrl</kbd>+<kbd>d</kbd> in lazygit's files panel.
|
||||
|
||||
### Sections
|
||||
|
||||
### Pull requests
|
||||
|
||||
| Title | Filter |
|
||||
| --- | --- |
|
||||
| My Pull Requests | `is:open author:@me` |
|
||||
| Needs My Review | `is:open review-requested:@me` |
|
||||
| Involved | `is:open involves:@me -author:@me` |
|
||||
|
||||
### Issues
|
||||
|
||||
| Title | Filter |
|
||||
| --- | --- |
|
||||
| My Issues | `is:open author:@me` |
|
||||
| Assigned | `is:open assignee:@me` |
|
||||
| Involved | `is:open involves:@me -author:@me` |
|
||||
|
||||
### Notifications
|
||||
|
||||
| Title | Filter |
|
||||
| --- | --- |
|
||||
| All | (none) |
|
||||
| Created | `reason:author` |
|
||||
| Participating | `reason:participating` |
|
||||
| Mentioned | `reason:mention` |
|
||||
| Review Requested | `reason:review-requested` |
|
||||
| Assigned | `reason:assign` |
|
||||
| Subscribed | `reason:subscribed` |
|
||||
| Team Mentioned | `reason:team-mention` |
|
||||
|
||||
### Defaults
|
||||
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| `defaults.view` | `prs` |
|
||||
| `defaults.preview.open` / `width` | `true` / `0.45` |
|
||||
| `defaults.prsLimit`, `issuesLimit`, `notificationsLimit` | `20` each |
|
||||
| `defaults.prApproveComment` | `LGTM` |
|
||||
| `defaults.refetchIntervalMinutes` | `30` |
|
||||
| `repo.branchesRefetchIntervalSeconds` | `30` |
|
||||
| `repo.prsRefetchIntervalSeconds` | `60` |
|
||||
| `repoPaths` | empty |
|
||||
| `confirmQuit` | `false` |
|
||||
| `showAuthorIcons` | `true` |
|
||||
| `smartFilteringAtLaunch` | `true` |
|
||||
| `includeReadNotifications` | `true` |
|
||||
| `theme.ui.sectionsShowCount` | `true` |
|
||||
| `theme.ui.table.showSeparator` / `compact` | `true` / `false` |
|
||||
|
||||
### Column layout
|
||||
|
||||
| View | Column | Width | Hidden |
|
||||
| --- | --- | --- | --- |
|
||||
| PRs | `updatedAt`, `createdAt` | 5 | no |
|
||||
| PRs | `repo` | 20 | no |
|
||||
| PRs | `author` | 15 | no |
|
||||
| PRs | `authorIcon` | | no |
|
||||
| PRs | `assignees` | 20 | yes |
|
||||
| PRs | `base` | 15 | yes |
|
||||
| PRs | `lines` | 15 | no |
|
||||
| Issues | `updatedAt`, `createdAt` | 5 | no |
|
||||
| Issues | `repo` | 15 | no |
|
||||
| Issues | `creator` | 10 | no |
|
||||
| Issues | `creatorIcon` | | no |
|
||||
| Issues | `assignees` | 20 | yes |
|
||||
|
||||
### Keybindings
|
||||
|
||||
| View | Key | Command |
|
||||
| --- | --- | --- |
|
||||
| PRs | <kbd>T</kbd> | `gh enhance -R {{.RepoName}} {{.PrNumber}}` |
|
||||
|
||||
`gh enhance` is a gh extension that is not in the managed extension list; install it separately for this binding to work.
|
||||
|
||||
### Pager
|
||||
|
||||
**~/.config/gh-dash/config.yml**
|
||||
|
||||
```yaml
|
||||
pager:
|
||||
diff: "hunk pager"
|
||||
```
|
||||
|
||||
PR diffs open in [Hunk](hunk.md).
|
||||
|
||||
## Other integrations
|
||||
|
||||
- [Lazygit](lazygit.md) custom commands call `gh repo sync && gh clean-branches --force`, `gh commit`, `gh pr create --fill -w`, `gh dash`, and `gh browse`.
|
||||
- `ghn` creates github.com repos with `gh`, and `gh-sonr-org-migrate` scripts gh for an org migration.
|
||||
- The tmux window-name map gives `gh` its own Nerd Font icon.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Hunk
|
||||
|
||||
Hunk terminal diff viewer settings, used as the gh-dash diff pager.
|
||||
|
||||
[Hunk](https://github.com/modem-dev/hunk) is a review-first terminal diff viewer aimed at agent-authored changes. In this setup it is the diff pager for [gh-dash](github-cli.md#gh-dash).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Sources | `dot_config/hunk/config.toml`, `dot_config/hunk/private_state.json` |
|
||||
| Targets | `~/.config/hunk/config.toml`, `~/.config/hunk/state.json` (mode `0600`) |
|
||||
| Packages | Not declared in `packages.yaml`; install hunk separately |
|
||||
|
||||
## config.toml
|
||||
|
||||
**~/.config/hunk/config.toml**
|
||||
|
||||
```toml
|
||||
theme = "auto"
|
||||
mode = "auto"
|
||||
vcs = "git"
|
||||
watch = false
|
||||
exclude_untracked = true
|
||||
line_numbers = true
|
||||
tab_width = 4
|
||||
wrap_lines = false
|
||||
menu_bar = true
|
||||
agent_notes = true
|
||||
prompt_save_view_preferences = false
|
||||
transparent_background = true
|
||||
```
|
||||
|
||||
| Key | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `theme` | `auto` | Picks a built-in theme to match the terminal. |
|
||||
| `mode` | `auto` | Chooses split or stacked layout from the window width (alternatives: `split`, `stack`). |
|
||||
| `vcs` | `git` | Git backend (alternatives: `jj`, `sl`). |
|
||||
| `watch` | `false` | Does not reload on file changes. |
|
||||
| `exclude_untracked` | `true` | Untracked files are left out of working-tree diffs. |
|
||||
| `line_numbers` | `true` | Shows line numbers. |
|
||||
| `tab_width` | `4` | Tab stops every 4 columns. |
|
||||
| `wrap_lines` | `false` | Long lines are not wrapped. |
|
||||
| `menu_bar` | `true` | Shows the top menu bar. |
|
||||
| `agent_notes` | `true` | Shows agent notes when a review opens (off by default in hunk). |
|
||||
| `prompt_save_view_preferences` | `false` | Does not ask to persist view changes. |
|
||||
| `transparent_background` | `true` | Uses the terminal background instead of the theme's. |
|
||||
|
||||
No `[pager]` or per-command sections are set, so pager sessions use these top-level values.
|
||||
|
||||
## state.json
|
||||
|
||||
`private_state.json` is hunk's runtime state: the state-file `version` and `lastSeenCliVersion`, which hunk updates on its own. It holds no preferences. The `private_` prefix gives it mode `0600`.
|
||||
|
||||
## Integrations
|
||||
|
||||
**~/.config/gh-dash/config.yml**
|
||||
|
||||
```yaml
|
||||
pager:
|
||||
diff: "hunk pager"
|
||||
```
|
||||
|
||||
gh-dash pipes PR diffs into `hunk pager`. Plain `git diff` does not use hunk; it goes through diffnav (see [Git](git.md)).
|
||||
@@ -0,0 +1,47 @@
|
||||
# Lazydocker
|
||||
|
||||
Lazydocker with rounded borders and the CherryBlossom border and selection colors shared with lazygit.
|
||||
|
||||
[Lazydocker](https://github.com/jesseduffield/lazydocker) is a terminal UI for Docker containers, images, volumes, and compose projects. The config only restyles it to match [lazygit](lazygit.md).
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_config/lazydocker/config.yml` |
|
||||
| Target | `~/.config/lazydocker/config.yml` |
|
||||
| Packages | `lazydocker` (pacman) |
|
||||
|
||||
## Settings
|
||||
|
||||
**~/.config/lazydocker/config.yml**
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
border: rounded
|
||||
theme:
|
||||
activeBorderColor:
|
||||
- "#cf716f"
|
||||
inactiveBorderColor:
|
||||
- "#a6958f"
|
||||
selectedLineBgColor:
|
||||
- "#453130"
|
||||
optionsTextColor:
|
||||
- "#a6958f"
|
||||
```
|
||||
|
||||
| Key | Value | Palette |
|
||||
| --- | --- | --- |
|
||||
| `gui.border` | `rounded` | |
|
||||
| `activeBorderColor` | `#cf716f` | CherryBlossom primary |
|
||||
| `inactiveBorderColor` | `#a6958f` | CherryBlossom on-surface-variant |
|
||||
| `selectedLineBgColor` | `#453130` | CherryBlossom selection (primary 20% over surface) |
|
||||
| `optionsTextColor` | `#a6958f` | CherryBlossom on-surface-variant |
|
||||
|
||||
Everything else (keybindings, commands, log settings) is left at lazydocker's defaults.
|
||||
|
||||
## Launching
|
||||
|
||||
| Entry point | Behavior |
|
||||
| --- | --- |
|
||||
| `dd` | Alias for `lazydocker` in fish, bash, and zsh. It shadows the coreutils `dd` binary wherever the aliases are loaded; use `command dd` for the real one. |
|
||||
|
||||
tmux-resurrect lists `lazydocker` among the processes it restores, and the kitty tab bar shows a Docker icon while it runs.
|
||||
@@ -0,0 +1,282 @@
|
||||
# Lazygit
|
||||
|
||||
Lazygit with CherryBlossom colors, delta diffs, a full keybinding map, and custom commands wired to gh and helper scripts.
|
||||
|
||||
[Lazygit](https://github.com/jesseduffield/lazygit) is a terminal UI for git. The config sets CherryBlossom colors, renders diffs with delta, turns on background fetching and a whole-graph log, and adds custom commands that call `gh`.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_config/lazygit/config.yml`, `dot_config/lazygit/ii-theme.yml` |
|
||||
| Target | `~/.config/lazygit/config.yml`, `~/.config/lazygit/ii-theme.yml` |
|
||||
| Packages | `lazygit` (pacman) |
|
||||
| Ignored | `.config/lazygit/.omc/**` (agent tooling state) |
|
||||
|
||||
## Launching
|
||||
|
||||
| Entry point | Behavior |
|
||||
| --- | --- |
|
||||
| `gg` | Alias for `lazygit` (all shells) |
|
||||
| <kbd>Ctrl</kbd>+<kbd>g</kbd> | fish and zsh keybinding |
|
||||
| <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>g</kbd> | [Kitty](../terminal/kitty.md): lazygit as an overlay on the current window. The tmux-host kitty config opens it in a 90% `tmux display-popup` instead. |
|
||||
|
||||
tmux-resurrect lists `lazygit` among the processes it restores.
|
||||
|
||||
## GUI
|
||||
|
||||
Colors come from the CherryBlossom palette (`palettes/CherryBlossom.json` in [prdlk/noctalia](https://github.com/prdlk/noctalia)):
|
||||
|
||||
| Theme key | Color |
|
||||
| --- | --- |
|
||||
| `activeBorderColor` | `#cf716f` |
|
||||
| `inactiveBorderColor` | `#a6958f` |
|
||||
| `searchingActiveBorderColor` | `#c17e4a` |
|
||||
| `optionsTextColor` | `#a6958f` |
|
||||
| `selectedLineBgColor` | `#453130` |
|
||||
| `cherryPickedCommitBgColor` | `#453130` |
|
||||
| `cherryPickedCommitFgColor` | `#ad6756` |
|
||||
| `unstagedChangesColor` | `#ae825f` |
|
||||
| `defaultFgColor` | `#dcc1b7` |
|
||||
| `authorColors."*"` | `#dcc1b7` (every author the same color) |
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `border` | `rounded` |
|
||||
| `scrollHeight` / `scrollPastBottom` | `2` / `true` |
|
||||
| `scrollOffMargin` / `scrollOffBehavior` | `2` / `margin` |
|
||||
| `mouseEvents` | `true` |
|
||||
| `skipDiscardChangeWarning` | `true` |
|
||||
| `skipStashWarning` | `true` |
|
||||
| `skipNoStagedFilesWarning` | `false` |
|
||||
| `skipRewordInEditorWarning` | `false` |
|
||||
| `sidePanelWidth` | `0.4` |
|
||||
| `expandFocusedSidePanel` / `expandedSidePanelWeight` | `true` / `2` |
|
||||
| `mainPanelSplitMode` | `horizontal` |
|
||||
| `enlargedSideViewLocation` | `left` |
|
||||
| `language` | `auto` |
|
||||
| `timeFormat` / `shortTimeFormat` | `02 Jan 06` / `3:04PM` |
|
||||
| `commitLength.show` | `true` |
|
||||
| `showListFooter` | `true` |
|
||||
| `showFileTree` | `true` |
|
||||
| `showRandomTip` | `false` |
|
||||
| `showCommandLog` / `commandLogSize` | `true` / `4` |
|
||||
| `showBottomLine` | `false` (keybinding hint line hidden) |
|
||||
| `showPanelJumps` | `false` |
|
||||
| `showIcons`, `nerdFontsVersion`, `showFileIcons` | `true`, `"3"`, `true` |
|
||||
| `commitAuthorShortLength` / `commitAuthorLongLength` | `2` (initials) / `17` |
|
||||
| `commitHashLength` | `8` |
|
||||
| `showBranchCommitHash` | `true` |
|
||||
| `showDivergenceFromBaseBranch` | `arrowAndNumber` |
|
||||
| `splitDiff` | `auto` |
|
||||
| `screenMode` | `normal` |
|
||||
| `animateExplosion` | `true` |
|
||||
| `portraitMode` | `auto` |
|
||||
| `filterMode` | `fuzzy` |
|
||||
| `spinner.frames` / `spinner.rate` | ASCII bar (`\|`, `/`, `-`, `\`) / `50` ms |
|
||||
| `statusPanelView` | `allBranchesLog` |
|
||||
|
||||
## Git
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `diffRenderers` | `delta --dark --paging=never` with `colorArg: always`. delta also reads the `[delta]` section of `~/.gitconfig`; see [Git](git.md). |
|
||||
| `commit.signOff` | `false` |
|
||||
| `commit.autoWrapCommitMessage` / `autoWrapWidth` | `true` / `72` |
|
||||
| `merging.manualCommit` / `args` | `false` / empty |
|
||||
| `merging.squashMergeMessage` | `Squash merge {{selectedRef}} into {{currentBranch}}` |
|
||||
| `mainBranches` | `develop` |
|
||||
| `skipHookPrefix` | `WIP` (a message starting with `WIP` skips pre-commit hooks) |
|
||||
| `autoFetch` / `autoRefresh` / `fetchAll` | `true` / `true` / `true` |
|
||||
| `branchLogCmd` | `git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --` |
|
||||
| `allBranchesLogCmds` | `git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium` |
|
||||
| `overrideGpg` | `false` |
|
||||
| `disableForcePushing` | `false` |
|
||||
| `commitPrefix` / `branchPrefix` | empty |
|
||||
| `parseEmoji` | `true` |
|
||||
| `log.order` / `showGraph` / `showWholeGraph` | `topo-order` / `always` / `true` |
|
||||
| `truncateCopiedCommitHashesTo` | `8` |
|
||||
|
||||
## Refresh, updates, and OS
|
||||
|
||||
| Option | Value |
|
||||
| --- | --- |
|
||||
| `update.method` / `days` | `background` / `14` |
|
||||
| `refresher.refreshInterval` | `10` s |
|
||||
| `refresher.fetchInterval` | `60` s |
|
||||
| `confirmOnQuit` | `false` |
|
||||
| `quitOnTopLevelReturn` | `true` (<kbd>Esc</kbd> at top level quits) |
|
||||
| `disableStartupPopups` | `true` |
|
||||
| `notARepository` | `skip` (opens the most recent repo) |
|
||||
| `promptToReturnFromSubprocess` | `false` |
|
||||
| `os.edit`, `editAtLine`, `editAtLineAndWait`, `openDirInEditor`, `editPreset`, `open` | empty (lazygit defaults, which follow `$EDITOR` = nvim) |
|
||||
| `os.openLink` | `xdg-open {{link}}` |
|
||||
| `os.copyToClipboardCmd` / `readFromClipboardCmd` | `wl-copy` / `wl-paste` |
|
||||
|
||||
> [!NOTE]
|
||||
> The link opener and the `wl-copy`/`wl-paste` clipboard commands need a Wayland desktop. On a server they are unavailable, so opening links and clipboard actions fail there.
|
||||
|
||||
## Custom commands
|
||||
|
||||
| Key | Context | Command | Output |
|
||||
| --- | --- | --- | --- |
|
||||
| <kbd>Ctrl</kbd>+<kbd>i</kbd> | global | `gitv {{.Repository.Owner}}/{{.Repository.Name}}` | terminal |
|
||||
| <kbd>Ctrl</kbd>+<kbd>w</kbd> | global | `gwm` | terminal |
|
||||
| <kbd>Ctrl</kbd>+<kbd>a</kbd> | global | `wrkflw` | terminal |
|
||||
| <kbd>Ctrl</kbd>+<kbd>p</kbd> | global | `github prs` | terminal |
|
||||
| <kbd>F</kbd> | files | `gh repo sync && gh clean-branches --force` ("Sync repo and clean branches, update DuckDB cache") | log |
|
||||
| <kbd>A</kbd> | files | `gh commit` ("Smart commit with AI") | terminal |
|
||||
| <kbd>C</kbd> | files | `gh commit` ("Smart commit with AI") | terminal |
|
||||
| <kbd>a</kbd> | files | `git add` the file if it has unstaged changes, else `git reset` it ("Toggle file staged") | |
|
||||
| <kbd>L</kbd> | files | `livediff` | terminal |
|
||||
| <kbd>Ctrl</kbd>+<kbd>d</kbd> | files | `gh dash` | terminal |
|
||||
| <kbd>Ctrl</kbd>+<kbd>o</kbd> | files | `gh browse` | |
|
||||
| <kbd>o</kbd> | localBranches | `gh pr create --fill -w` ("start ai pull request") | |
|
||||
| <kbd>C</kbd> | localBranches | `gh repo sync && gh clean-branches --force` | log |
|
||||
| <kbd>O</kbd> | commits | `hub browse -- "commit/{{.SelectedLocalCommit.Hash}}"` | |
|
||||
|
||||
`gh commit`, `gh clean-branches`, and `gh dash` are managed gh extensions (see [GitHub CLI](github-cli.md)). `gitv`, `gwm`, `wrkflw`, `github`, `livediff`, and `hub` are not installed by this repo.
|
||||
|
||||
> [!WARNING]
|
||||
> Several custom keys share a key with a built-in binding in the same context: <kbd>C</kbd> and <kbd>a</kbd> in files (`commitChangesWithEditor`, `toggleStagedAll`), <kbd>Ctrl</kbd>+<kbd>w</kbd>, <kbd>Ctrl</kbd>+<kbd>p</kbd>, <kbd>Ctrl</kbd>+<kbd>d</kbd>, <kbd>Ctrl</kbd>+<kbd>o</kbd> against universal bindings, and <kbd>O</kbd> in commits. <kbd>Ctrl</kbd>+<kbd>i</kbd> is the same keycode as <kbd>Tab</kbd> in most terminals.
|
||||
|
||||
## Keybindings
|
||||
|
||||
The file writes out the full keybinding map. Most values match lazygit's defaults; they are listed so the file is the single reference.
|
||||
|
||||
### Universal
|
||||
|
||||
| Action | Key |
|
||||
| --- | --- |
|
||||
| `quit` / `quit-alt1` | <kbd>q</kbd> / <kbd>Ctrl</kbd>+<kbd>q</kbd> |
|
||||
| `return` | <kbd>Esc</kbd> |
|
||||
| `quitWithoutChangingDirectory` | <kbd>Q</kbd> |
|
||||
| `togglePanel` | <kbd>Tab</kbd> |
|
||||
| `prevItem` / `nextItem` | <kbd>Up</kbd> / <kbd>Down</kbd> |
|
||||
| `prevItem-alt` / `nextItem-alt` | <kbd>k</kbd> / <kbd>j</kbd> |
|
||||
| `prevPage` / `nextPage` | <kbd>,</kbd> / <kbd>.</kbd> |
|
||||
| `scrollLeft` / `scrollRight` | <kbd>H</kbd> / <kbd>L</kbd> |
|
||||
| `gotoTop` / `gotoBottom` | `<` / `>` |
|
||||
| `toggleRangeSelect` | <kbd>v</kbd> |
|
||||
| `rangeSelectDown` / `rangeSelectUp` | <kbd>Shift</kbd>+<kbd>Down</kbd> / <kbd>Shift</kbd>+<kbd>Up</kbd> |
|
||||
| `prevBlock` / `nextBlock` | <kbd>Left</kbd> / <kbd>Right</kbd> |
|
||||
| `prevBlock-alt` / `nextBlock-alt` | <kbd>h</kbd> / <kbd>l</kbd> |
|
||||
| `nextBlock-alt2` / `prevBlock-alt2` | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> |
|
||||
| `jumpToBlock` | <kbd>1</kbd>–<kbd>5</kbd> |
|
||||
| `nextMatch` / `prevMatch` | <kbd>n</kbd> / <kbd>N</kbd> |
|
||||
| `startSearch` | <kbd>/</kbd> |
|
||||
| `optionMenu` | <kbd>?</kbd> |
|
||||
| `select` | <kbd>Space</kbd> |
|
||||
| `goInto` / `confirm` | <kbd>Enter</kbd> |
|
||||
| `confirmInEditor` | <kbd>Alt</kbd>+<kbd>Enter</kbd> |
|
||||
| `remove` | <kbd>d</kbd> |
|
||||
| `new` | <kbd>n</kbd> |
|
||||
| `edit` | <kbd>e</kbd> |
|
||||
| `openFile` | <kbd>o</kbd> |
|
||||
| `scrollUpMain` / `scrollDownMain` | <kbd>PgUp</kbd> / <kbd>PgDn</kbd> |
|
||||
| `scrollUpMain-alt1` / `scrollDownMain-alt1` | <kbd>K</kbd> / <kbd>J</kbd> |
|
||||
| `scrollUpMain-alt2` / `scrollDownMain-alt2` | <kbd>Ctrl</kbd>+<kbd>u</kbd> / <kbd>Ctrl</kbd>+<kbd>d</kbd> |
|
||||
| `createRebaseOptionsMenu` | <kbd>m</kbd> |
|
||||
| `pushFiles` / `pullFiles` | <kbd>P</kbd> / <kbd>p</kbd> |
|
||||
| `refresh` | <kbd>R</kbd> |
|
||||
| `createPatchOptionsMenu` | <kbd>Ctrl</kbd>+<kbd>p</kbd> |
|
||||
| `nextTab` / `prevTab` | <kbd>]</kbd> / <kbd>[</kbd> |
|
||||
| `nextScreenMode` / `prevScreenMode` | <kbd>+</kbd> / <kbd>_</kbd> |
|
||||
| `undo` / `redo` | <kbd>z</kbd> / <kbd>Ctrl</kbd>+<kbd>z</kbd> |
|
||||
| `filteringMenu` | <kbd>Ctrl</kbd>+<kbd>s</kbd> |
|
||||
| `diffingMenu` / `diffingMenu-alt` | <kbd>W</kbd> / <kbd>Ctrl</kbd>+<kbd>e</kbd> |
|
||||
| `copyToClipboard` | <kbd>Ctrl</kbd>+<kbd>o</kbd> |
|
||||
| `openRecentRepos` | <kbd>Ctrl</kbd>+<kbd>r</kbd> |
|
||||
| `submitEditorText` | <kbd>Enter</kbd> |
|
||||
| `extrasMenu` | <kbd>@</kbd> |
|
||||
| `toggleWhitespaceInDiffView` | <kbd>Ctrl</kbd>+<kbd>w</kbd> |
|
||||
| `increaseContextInDiffView` / `decreaseContextInDiffView` | `}` / `{` |
|
||||
| `increaseRenameSimilarityThreshold` / `decrease...` | <kbd>)</kbd> / <kbd>(</kbd> |
|
||||
| `openDiffTool` | <kbd>Ctrl</kbd>+<kbd>t</kbd> (runs the nvim `codediff` difftool) |
|
||||
| `newWorktree` | <kbd>w</kbd> |
|
||||
|
||||
### Status
|
||||
|
||||
| Action | Key |
|
||||
| --- | --- |
|
||||
| `checkForUpdate` | <kbd>u</kbd> |
|
||||
| `recentRepos` | <kbd>Enter</kbd> |
|
||||
| `allBranchesLogGraph` | <kbd>a</kbd> |
|
||||
|
||||
### Files
|
||||
|
||||
| Action | Key |
|
||||
| --- | --- |
|
||||
| `commitChanges` | <kbd>c</kbd> |
|
||||
| `commitChangesWithoutHook` | <kbd>w</kbd> |
|
||||
| `amendLastCommit` | <kbd>E</kbd> |
|
||||
| `commitChangesWithEditor` | <kbd>C</kbd> |
|
||||
| `findBaseCommitForFixup` | <kbd>Ctrl</kbd>+<kbd>f</kbd> |
|
||||
| `confirmDiscard` | <kbd>x</kbd> |
|
||||
| `ignoreFile` | <kbd>i</kbd> |
|
||||
| `refreshFiles` | <kbd>r</kbd> |
|
||||
| `stashAllChanges` / `viewStashOptions` | <kbd>s</kbd> / <kbd>S</kbd> |
|
||||
| `toggleStagedAll` | <kbd>a</kbd> |
|
||||
| `viewResetOptions` | <kbd>D</kbd> |
|
||||
| `fetch` | <kbd>f</kbd> |
|
||||
| `toggleTreeView` | `` ` `` |
|
||||
| `openMergeOptions` | <kbd>M</kbd> |
|
||||
| `openStatusFilter` | <kbd>Ctrl</kbd>+<kbd>b</kbd> |
|
||||
| `copyFileInfoToClipboard` | <kbd>y</kbd> |
|
||||
|
||||
### Branches
|
||||
|
||||
| Action | Key |
|
||||
| --- | --- |
|
||||
| `createPullRequest` | <kbd>1</kbd> |
|
||||
| `viewPullRequestOptions` | <kbd>O</kbd> |
|
||||
| `copyPullRequestURL` | <kbd>Ctrl</kbd>+<kbd>y</kbd> |
|
||||
| `checkoutBranchByName` | <kbd>c</kbd> |
|
||||
| `forceCheckoutBranch` | <kbd>F</kbd> |
|
||||
| `rebaseBranch` | <kbd>r</kbd> |
|
||||
| `renameBranch` | <kbd>R</kbd> |
|
||||
| `mergeIntoCurrentBranch` | <kbd>M</kbd> |
|
||||
| `viewGitFlowOptions` | <kbd>i</kbd> |
|
||||
| `fastForward` / `fetchRemote` | <kbd>f</kbd> |
|
||||
| `createTag` / `pushTag` | <kbd>T</kbd> / <kbd>P</kbd> |
|
||||
| `setUpstream` | <kbd>u</kbd> |
|
||||
| `sortOrder` | <kbd>s</kbd> |
|
||||
|
||||
### Commits
|
||||
|
||||
| Action | Key |
|
||||
| --- | --- |
|
||||
| `squashDown` | <kbd>s</kbd> |
|
||||
| `renameCommit` / `renameCommitWithEditor` | <kbd>r</kbd> / <kbd>R</kbd> |
|
||||
| `viewResetOptions` | <kbd>g</kbd> |
|
||||
| `markCommitAsFixup` / `createFixupCommit` | <kbd>f</kbd> / <kbd>F</kbd> |
|
||||
| `squashAboveCommits` | <kbd>S</kbd> |
|
||||
| `moveDownCommit` / `moveUpCommit` | <kbd>Ctrl</kbd>+<kbd>j</kbd> / <kbd>Ctrl</kbd>+<kbd>k</kbd> |
|
||||
| `resetCommitAuthor` | <kbd>a</kbd> |
|
||||
| `pickCommit` | <kbd>p</kbd> |
|
||||
| `revertCommit` | <kbd>t</kbd> |
|
||||
| `cherryPickCopy` / `pasteCommits` | <kbd>C</kbd> / <kbd>V</kbd> |
|
||||
| `markCommitAsBaseForRebase` | <kbd>B</kbd> |
|
||||
| `tagCommit` | <kbd>T</kbd> |
|
||||
| `checkoutCommit` | <kbd>Space</kbd> |
|
||||
| `resetCherryPick` | <kbd>Ctrl</kbd>+<kbd>c</kbd> |
|
||||
| `copyCommitAttributeToClipboard` | <kbd>y</kbd> |
|
||||
| `openLogMenu` | <kbd>Ctrl</kbd>+<kbd>l</kbd> |
|
||||
| `openInBrowser` | <kbd>o</kbd> |
|
||||
| `viewBisectOptions` | <kbd>b</kbd> |
|
||||
| `startInteractiveRebase` | <kbd>i</kbd> |
|
||||
|
||||
### Other panels
|
||||
|
||||
| Panel | Action | Key |
|
||||
| --- | --- | --- |
|
||||
| amendAttribute | `resetAuthor` / `setAuthor` / `addCoAuthor` | <kbd>a</kbd> / <kbd>A</kbd> / <kbd>c</kbd> |
|
||||
| stash | `popStash` / `renameStash` | <kbd>g</kbd> / <kbd>r</kbd> |
|
||||
| commitFiles | `checkoutCommitFile` | <kbd>c</kbd> |
|
||||
| main | `toggleSelectHunk` / `pickBothHunks` / `editSelectHunk` | <kbd>a</kbd> / <kbd>b</kbd> / <kbd>E</kbd> |
|
||||
| submodules | `init` / `update` / `bulkMenu` | <kbd>i</kbd> / <kbd>u</kbd> / <kbd>b</kbd> |
|
||||
| commitMessage | `commitMenu` | <kbd>Ctrl</kbd>+<kbd>o</kbd> |
|
||||
|
||||
## ii-theme.yml
|
||||
|
||||
`ii-theme.yml` is generated by the ii wallpaper theming system and holds only a `gui.theme` block: white active border (bold), grey `#8F9192` inactive border, `#1F1F1F` selection and cherry-pick backgrounds, `#FFF17B` marked base commit, `#FF9BAD` unstaged changes, `#DFE3E3` default text.
|
||||
|
||||
Nothing in the repo loads it, so `config.yml`'s cyberdream colors are what lazygit uses. To apply it, pass both files: `LG_CONFIG_FILE="$HOME/.config/lazygit/config.yml,$HOME/.config/lazygit/ii-theme.yml" lazygit`.
|
||||
@@ -0,0 +1,81 @@
|
||||
# LeetCode
|
||||
|
||||
LeetCode CLI workspace config, its tracked state files, and the lc-elir helper that sends solutions to omp.
|
||||
|
||||
The repo tracks the state directory of a terminal LeetCode client, plus a helper that feeds solution files to omp prompt commands. The `~/.leetcode` layout (workspaces with `collab.json`, `timer.json`, and `snapshots/`) matches [night-slayer18/leetcode-cli](https://github.com/night-slayer18/leetcode-cli), which is invoked as `leetcode`.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_leetcode/` |
|
||||
| Target | `~/.leetcode/` |
|
||||
| Packages | The CLI is not declared in `packages.yaml`; install it separately |
|
||||
| Alias | `lc` = `leetcode` (fish, bash, zsh) |
|
||||
|
||||
```text
|
||||
- dot_leetcode/
|
||||
- workspaces.json
|
||||
- star-prompt.json
|
||||
- version-cache.json
|
||||
- workspaces/
|
||||
- default/
|
||||
- config.json
|
||||
- collab.json
|
||||
- timer.json
|
||||
- snapshots/
|
||||
- .keep
|
||||
```
|
||||
|
||||
## Workspaces
|
||||
|
||||
`workspaces.json` lists one workspace, `default`, and marks it active.
|
||||
|
||||
**~/.leetcode/workspaces/default/config.json**
|
||||
|
||||
```json
|
||||
{
|
||||
"workDir": "/home/prad/Code/github.com/prdlk/leetcode/work/1",
|
||||
"lang": "python3",
|
||||
"site": "leetcode.com",
|
||||
"editor": "nvim",
|
||||
"syncRepo": "https://github.com/prdlk/leetcode.git"
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Value | Effect |
|
||||
| --- | --- | --- |
|
||||
| `workDir` | `~/Code/github.com/prdlk/leetcode/work/1` (absolute path in the file) | Where problem files are written. It sits inside the ghq checkout of the solutions repo, under `$GHQ_ROOT`. |
|
||||
| `lang` | `python3` | Default solution language. |
|
||||
| `site` | `leetcode.com` | Target site. |
|
||||
| `editor` | `nvim` | Opens problems in [Neovim](neovim.md). |
|
||||
| `syncRepo` | `https://github.com/prdlk/leetcode.git` | Repo that solutions sync to. |
|
||||
|
||||
The path is absolute, so on a machine with a different username or `$GHQ_ROOT`, edit `workDir`.
|
||||
|
||||
The other workspace files start empty: `collab.json` has `"session": null`, `timer.json` has no solve times and no active timer, and `snapshots/` holds only a `.keep` placeholder.
|
||||
|
||||
## Tracked state files
|
||||
|
||||
| File | Contents |
|
||||
| --- | --- |
|
||||
| `star-prompt.json` | Counter for the CLI's "star the repo" prompt: accepted-submission total, milestones already shown, submissions since the last prompt, timestamps, and a `dismissed` flag. |
|
||||
| `version-cache.json` | Last update check time, latest known version, and whether it has breaking changes. |
|
||||
|
||||
These are runtime files. In chezmoi's symlink mode, non-template, non-private files are symlinks into the source directory, so the CLI writes these straight into the repo working tree. Expect them to show up as diffs.
|
||||
|
||||
## lc-elir
|
||||
|
||||
`lc-elir [root]` picks LeetCode solution files with fzf and runs the omp `/leetcode-eli5` command on each one.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Sources | `dot_local/bin/executable_lc-elir` (POSIX sh), `dot_config/fish/functions/lc-elir.fish` (fish port) |
|
||||
| Targets | `~/.local/bin/lc-elir`, `~/.config/fish/functions/lc-elir.fish` |
|
||||
| Needs | `fd`, `fzf`, `bat`, `omp` |
|
||||
|
||||
1. **Find candidates**: `fd --type f '^\d+\..+\.(js|ts)$'` under `root` (default `.`) matches numbered problem files such as `1365.some-slug.js`.
|
||||
2. **Pick**: `fzf --multi` with prompt `eli5> `, showing the last three path segments (`--delimiter=/ --with-nth=-3..`). The right-hand 60% preview shows the first 40 lines through `bat --color=always --line-range=:40`.
|
||||
3. **Run**: For each selected file it prints a separator line, then runs `omp -p "/leetcode-eli5 <file>"`. It exits 1 if nothing was picked.
|
||||
|
||||
## omp commands
|
||||
|
||||
`lc-elir` runs the `/leetcode-eli5` omp command, which lives in `~/.omp/agent/commands/` and is not managed by this repo.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Neovim
|
||||
|
||||
Neovim config pulled from its own repository as a chezmoi external, plus the editor hooks the dotfiles wire into it.
|
||||
|
||||
Neovim is the default editor everywhere in this setup. Its configuration (AstroNvim-based, per the repo README) lives in a separate repository, [prdlk/nvim](https://github.com/prdlk/nvim). This repo only clones it and wires other tools to it; the Lua config itself is not documented here.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `.chezmoiexternal.toml` entry `.config/nvim` |
|
||||
| Target | `~/.config/nvim` (a git checkout) |
|
||||
| Remote | `https://github.com/prdlk/nvim.git` |
|
||||
| Packages | `neovim` (pacman) |
|
||||
|
||||
## How the external works
|
||||
|
||||
**.chezmoiexternal.toml**
|
||||
|
||||
```toml
|
||||
[".config/nvim"]
|
||||
type = "git-repo"
|
||||
url = "https://github.com/prdlk/nvim.git"
|
||||
[".config/nvim".pull]
|
||||
args = ["--ff-only"]
|
||||
```
|
||||
|
||||
- `type = "git-repo"`: chezmoi runs `git clone` when `~/.config/nvim` does not exist, so a fresh machine gets the config on first apply.
|
||||
- No `refreshPeriod` is set, so a normal `chezmoi apply` never pulls. The local checkout always wins; commit and push nvim changes from `~/.config/nvim` directly.
|
||||
- A forced refresh (`chezmoi apply --refresh-externals`) runs `git pull --ff-only`, which refuses to merge if local history has diverged.
|
||||
|
||||
See [externals and ignore rules](../chezmoi/externals-and-ignore.md) for how chezmoi handles externals in general.
|
||||
|
||||
### Ignored runtime paths
|
||||
|
||||
`.chezmoiignore.tmpl` keeps chezmoi away from state that tools write inside the checkout:
|
||||
|
||||
| Path | Why |
|
||||
| --- | --- |
|
||||
| `.config/nvim/lazy-lock.json` | lazy.nvim lockfile, rewritten on plugin updates |
|
||||
| `.config/nvim/.git`, `.config/nvim/.git/**` | the checkout's own git metadata |
|
||||
| `.config/nvim/.omc/**` | agent tooling state |
|
||||
| `.config/nvim/.crush/**` | agent tooling state |
|
||||
| `.config/nvim/.understand-anything/**` | agent tooling state |
|
||||
|
||||
## Integrations in this repo
|
||||
|
||||
| Where | What |
|
||||
| --- | --- |
|
||||
| `dot_config/fish/conf.d/10-env.fish` | `EDITOR=nvim`. [gh](github-cli.md) falls back to it because its `editor` key is empty. |
|
||||
| [Aliases](../shell/aliases.md) | `e` = `nvim`; `e.v` opens `~/.config/nvim/lua` in yazi; `e.t` opens the tmux config in nvim. |
|
||||
| [Kitty](../terminal/kitty.md) | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>e</kbd> opens nvim in the current directory in a 50/50 vertical split. Kitty's `listen_on` socket lets nvim hand <kbd>Ctrl</kbd>+<kbd>h/j/k/l</kbd> at a window edge back to kitty. |
|
||||
| [Yazi](yazi.md) | The `edit` opener and <kbd>Ctrl</kbd>+<kbd>v</kbd> / <kbd>Ctrl</kbd>+<kbd>x</kbd> call `~/.local/bin/yazi-nvim-open`. Inside an nvim terminal (`$NVIM` set) files open in the host instance through the nvim-side `EditFromYazi` Lua function; otherwise a blocking `nvim` starts. |
|
||||
| [Git](git.md) | `git difftool` runs `nvim "$LOCAL" "$REMOTE" +"CodeDiff file $LOCAL $REMOTE"`. |
|
||||
| [LeetCode](leetcode.md) | The default workspace sets `"editor": "nvim"`. |
|
||||
|
||||
> [!NOTE]
|
||||
> `yazi-nvim-open` depends on `_G.EditFromYazi`, which the nvim repo defines (its header points at `lua/config/yazi_session.lua`). If that function is missing, the remote call fails and nothing opens.
|
||||
@@ -0,0 +1,182 @@
|
||||
# television
|
||||
|
||||
tv config symlinked from the repo, a transparent cyberdream theme, and the custom cables behind gho, pj, and ctrl-e in fish, zsh, and bash.
|
||||
|
||||
[television](https://github.com/alexpasmantier/television) (`tv`) is a fuzzy picker built on "cables": TOML files that define a source command, a preview, and actions. Every shell picker in the repo runs on a tv cable, with the same keys in fish, zsh, and bash.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/television](https://github.com/prdlk/television) via `.chezmoiexternals/television.toml`: `config.toml`, `cable/*.toml`, `themes/cyberdream-transparent.toml` |
|
||||
| Target | `~/.config/television/` |
|
||||
| Install | Not listed in `.chezmoidata/packages.yaml`; install separately |
|
||||
|
||||
`~/.config/television` is a `git-repo` checkout (pulled `--ff-only` every 168h), so edits apply immediately; commit and push them from there. `cable/` holds tv's stock cables plus the repo's own and overridden ones.
|
||||
|
||||
## config.toml
|
||||
|
||||
The UI is minimal: no borders, a hidden status bar, and padding in place of frames.
|
||||
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| `default_channel` | `files` |
|
||||
| `ui.theme` | `cyberdream-transparent` |
|
||||
| `ui.input_bar` | position `top`, prompt `❯`, `border_type = "none"`, padding left 1, right 1, bottom 1 |
|
||||
| `ui.status_bar.hidden` | `true` (<kbd>f12</kbd> toggles it) |
|
||||
| `ui.results_panel` | `border_type = "none"`, padding left 1, right 1 |
|
||||
| `ui.preview_panel` | `size = 50`, `scrollbar = false`, `border_type = "none"`, padding left 2, right 1 |
|
||||
| `ui.help_panel.hidden` | `true` |
|
||||
|
||||
tv 0.15 has no config key for inline mode (`--inline` and `--height` are CLI-only), so every call site passes `--inline`: `gho` (`tv ghq-repos --inline`), `pj` (`tv pj --inline …`), <kbd>ctrl-e</kbd> (`tv git-files --inline`), and bash <kbd>ctrl-t</kbd> (`tv --autocomplete-prompt … --inline`). Actions that re-exec tv pass it too: `files` (`tv files .. --inline`), `dirs` (`tv dirs .. --inline`), and `channels` (`tv {} --inline`). `tv --inline` opens on the line below the prompt and leaves the cursor there on exit; the shell key bindings move the prompt back ([fish](../shell/fish.md#key-bindings), [bash and zsh](../shell/bash-zsh.md)).
|
||||
|
||||
A channel that sets its own `[ui.preview_panel]` replaces the whole table with tv's defaults (rounded border, scrollbar). The stock cables that override it (`alias`, `cargo-commands`, `crontab`, `env`, `fonts`, `gh-issues`, `journal`, `man-pages`, `npm-scripts`, `opencode-sessions`, `ports`, `tea-issues`, `tea-prs`, `text`, `todo-comments`) therefore repeat `border_type = "none"`, `scrollbar = false`, and padding left 2, right 1.
|
||||
|
||||
Global keys added on top of the defaults:
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>ctrl-e</kbd> | `actions:edit`: the channel's `edit` action |
|
||||
| <kbd>ctrl-h</kbd> | `actions:goto_parent_dir`; the help panel loses <kbd>ctrl-h</kbd> |
|
||||
| <kbd>end</kbd> | `go_to_input_end` (unchanged) |
|
||||
|
||||
## Theme
|
||||
|
||||
`themes/cyberdream-transparent.toml` is a separate file because tv 0.15's builtin `cyberdream` shadows a user theme of the same name and paints an opaque `#16181a` background. This theme omits `background`, so kitty's translucent background shows through (tv 0.15 rejects `background = 'none'`).
|
||||
|
||||
| Key | Color |
|
||||
| --- | --- |
|
||||
| `border_fg` | `#3c4048` |
|
||||
| `text_fg`, `input_text_fg`, `result_value_fg` | `#ffffff` |
|
||||
| `dimmed_text_fg` | `#7b8496` |
|
||||
| `result_name_fg` | `#5ea1ff` |
|
||||
| `result_line_number_fg` | `#f1ff5e` |
|
||||
| `match_fg` | `#5ef1ff` |
|
||||
| `selection_fg` / `selection_bg` | `#5eff6c` on `#3c4048` |
|
||||
| `preview_title_fg` | `#ffaecf` |
|
||||
| `result_count_fg` | `#ffbd5e` |
|
||||
| `channel_mode_bg` | `#5ef1ff` on `#16181a` |
|
||||
| `remote_control_mode_bg` | `#5eff6c` on `#16181a` |
|
||||
| `action_picker_mode_bg` | `#bd5eff` on `#16181a` |
|
||||
|
||||
## Sorting
|
||||
|
||||
Every custom cable except `agents` (a fixed list, see [agents](#agents)), `omp-sessions` (sorted by [`omp-session`](../shell/scripts.md#omp-session) itself), and `jobs` (fish's job order) pipes its source through [`mtime-sort`](../shell/scripts.md#mtime-sort), which orders lines newest-first by the mtime of the path in the line's last tab-separated field (`--repo` stats `<path>/.git/index` first). tv then ranks match quality and frecency on top of that order. All custom cables set `shell = "bash"`, because `$SHELL` is fish.
|
||||
|
||||
## Custom cables
|
||||
|
||||
| Cable | File | Opened by |
|
||||
| --- | --- | --- |
|
||||
| [`ghq-repos`](#ghq-repos) | `cable/ghq-repos.toml` | <kbd>ctrl-o</kbd> (`gho`) |
|
||||
| [`git-files`](#git-files) | `cable/git-files.toml` | <kbd>ctrl-e</kbd> |
|
||||
| [`pj`](#pj) | `cable/pj.toml` | <kbd>ctrl-p</kbd>, <kbd>down</kbd> on an empty prompt (`pj`) |
|
||||
| [`files`](#files) | `cable/files.toml` | `tv`, <kbd>ctrl-t</kbd> autocomplete |
|
||||
| [`obsidian`](#obsidian) | `cable/obsidian.toml` | `tv obsidian` |
|
||||
| [`agents`](#agents) | `cable/agents.toml` | `tv agents` |
|
||||
| [`omp-sessions`](#omp-sessions) | `cable/omp-sessions.toml` | `tv omp-sessions`, <kbd>ctrl-a</kbd> (`omp-session`) when it has nothing to resume |
|
||||
| [`jobs`](#jobs) | `cable/jobs.toml` | <kbd>ctrl-z</kbd> in fish (`_jobs_bind`) with two or more background jobs |
|
||||
| [`kitty-days`](#kitty-days) | `cable/kitty-days.toml` | `tv kitty-days` |
|
||||
|
||||
The keys are bound in fish (`fish_user_key_bindings.fish`), zsh (`keybinds.zsh`), and bash (`keybinds.bash`); bash has no <kbd>ctrl-a</kbd> binding, and <kbd>ctrl-z</kbd> is fish only. See [fish](../shell/fish.md#key-bindings) and [bash and zsh](../shell/bash-zsh.md).
|
||||
|
||||
### ghq-repos
|
||||
|
||||
Entries are `name<TAB>path`: tv shows and matches the name and prints the path. <kbd>ctrl-s</kbd> cycles between the two sources.
|
||||
|
||||
| Source | Entries | Sort |
|
||||
| --- | --- | --- |
|
||||
| Repos | `ghq list` under `ghq root`, plus two checkouts outside it: the chezmoi source (`~/.local/share/chezmoi`) as `prad.codes/env/cachyos` and the nvim config (`~/.config/nvim`) as `github.com/prdlk/nvim`. Those two win over a ghq clone with the same name | `mtime-sort --repo` |
|
||||
| Vaults | Every directory under `~/Documents/Obsidian` (depth 3) that contains `.obsidian`, named relative to that directory | `mtime-sort` |
|
||||
|
||||
**Preview.** Two commands; <kbd>ctrl-f</kbd> cycles. The default is the commit graph for git directories: `git log --graph --all`, newest 300 commits, as short hash, relative date, subject and refs. The second is `onefetch --no-art`. Non-git directories show `eza --tree --level 2` (falling back to `ls -la`) in both.
|
||||
|
||||
[serie](https://github.com/lusingander/serie) cannot draw inside the preview pane: it is a fullscreen TUI that paints its graph with kitty images and has no print mode. <kbd>ctrl-g</kbd> opens it instead.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>enter</kbd> | Print the path. The `gho` function cds into it, clears, and runs `onefetch` in git repos |
|
||||
| <kbd>ctrl-e</kbd> | `edit`: `cd <path> && nvim .` on `/dev/tty` |
|
||||
| <kbd>ctrl-b</kbd> | `browse`: `cd <path> && gop` in fork mode, so tv stays open ([gop](../shell/scripts.md#gop)) |
|
||||
| <kbd>ctrl-g</kbd> | `history`: `serie` on `/dev/tty` in fork mode for git directories; <kbd>q</kbd> returns to the picker |
|
||||
|
||||
**Needs:** `ghq`, `fd`, `git`, `serie`, `onefetch`, `mtime-sort`.
|
||||
|
||||
### git-files
|
||||
|
||||
| Source | Entries |
|
||||
| --- | --- |
|
||||
| Tracked | `git ls-files` of the whole repo (from the toplevel), or `fd -t f` outside a repo |
|
||||
| All | `fd -t f -H -E .git` |
|
||||
|
||||
Both are sorted with `mtime-sort`. The preview is `bat -n` with `BAT_THEME=ansi`. <kbd>enter</kbd> runs `edit`: `nvim {}` on `/dev/tty`.
|
||||
|
||||
**Needs:** `git`, `fd`, `bat`, `mtime-sort`.
|
||||
|
||||
### pj
|
||||
|
||||
Source `pj --list`; tv shows the display column. The preview runs `pj --preview` in the global 50% panel, and <kbd>enter</kbd> runs `pj --run`. Running `pj` scans the project and opens this cable with its own records. See [pj](../shell/scripts.md#pj).
|
||||
|
||||
**Needs:** `pj`, `mtime-sort`.
|
||||
|
||||
### files
|
||||
|
||||
Overrides tv's stock `files` cable. Sources `Default` (`fd -t f`) and `Hidden` (`fd -t f -H`), both piped through `mtime-sort`. The `edit` action opens the selection in `nvim` on `/dev/tty`, so it also works from <kbd>ctrl-t</kbd>, where the shell captures tv's stdout.
|
||||
|
||||
**Needs:** `fd`, `bat`, `mtime-sort`.
|
||||
|
||||
### obsidian
|
||||
|
||||
Entries are `name<TAB>path` for every directory under `~/Documents/Obsidian` (depth 3) that contains `.obsidian`, named relative to that directory (nested vaults show as `Research/Sonr`) and sorted with `mtime-sort`. The preview is `eza --tree --level 2`.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>enter</kbd> | `open`: `xdg-open obsidian://open?path=<path>`, detached with `setsid -f` because the generic `xdg-open` stays in the foreground |
|
||||
| <kbd>ctrl-e</kbd> | `edit`: `cd <path> && nvim .` |
|
||||
|
||||
`obsidian://open?path=` only resolves vaults listed in `~/.config/obsidian/obsidian.json`, and Obsidian reads that file once at startup and rewrites it from memory. So when the vault is not listed and Obsidian is closed, `open` adds `{path, ts}` under a random 16-hex id first. When Obsidian is running, it sends a notification and opens the vault switcher (`obsidian://choose-vault`) for **Open folder as vault**.
|
||||
|
||||
**Needs:** `fd`, `jq`, `eza`, `obsidian`, `xdg-open`, `mtime-sort`.
|
||||
|
||||
### agents
|
||||
|
||||
The source is a table of known agent CLIs in the cable, `binary|launch command|description`, kept in table order (`omp` first). A row is listed only when `command -v <binary>` resolves, so agents appear and disappear as they are installed. Entries are `<name description><TAB><launch command><TAB><binary>`; the description is part of the matched text, so `openai` finds `codex`.
|
||||
|
||||
| Agents in the table | Launch |
|
||||
| --- | --- |
|
||||
| `omp`, `claude`, `codex`, `opencode`, `crush`, `pi`, `cursor-agent`, `copilot`, `gemini`, `agy`, `grok`, `muse`, `ori`, `hermes`, `amp`, `droid`, `qwen`, `kimi`, `goose`, `aider`, `auggie`, `kiro-cli`, `cn`, `fractal` | The binary, except `claude --dangerously-skip-permissions` (same as `ai`) |
|
||||
|
||||
The preview shows the launch command, the path on `PATH`, its `readlink -f` target, and, for the `~/.local/bin` wrappers that install the agent through mise on first run, the mise tool. It never runs the agent, since some wrappers take seconds to answer `--version`.
|
||||
|
||||
<kbd>enter</kbd> runs `launch` in execute mode: tv exits and the launch command runs in its place, in the current directory.
|
||||
|
||||
**Needs:** nothing beyond the agents themselves.
|
||||
|
||||
### omp-sessions
|
||||
|
||||
Source `omp-session --list`: the omp sessions of the current git repo, newest first, as `<age> <branch> <title>[ · <subdir>]<TAB><session file>`. tv shows and matches the display column, so typing a branch name filters to its sessions. The branch is the one HEAD was on when the session was last written, from the reflog; see [omp-session](../shell/scripts.md#omp-session).
|
||||
|
||||
The preview runs `omp-session --preview`: title, last-write time and cwd, then the text of every user and assistant message. <kbd>enter</kbd> runs `resume` in execute mode, `omp-session --resume <file>`, which `exec`s `omp --resume <file>`. <kbd>ctrl-n</kbd> runs `new`, `omp-session --new`, which starts a fresh `omp` session. In this cable <kbd>ctrl-n</kbd> no longer moves to the next entry; <kbd>down</kbd> and <kbd>ctrl-j</kbd> still do.
|
||||
|
||||
**Needs:** `omp-session`, `omp`.
|
||||
|
||||
### jobs
|
||||
|
||||
The current fish shell's background jobs, stopped or running, newest first. tv runs as a child process and cannot read the shell's job table, so `_jobs_bind` ([fish](../shell/fish.md#key-bindings)) writes it to a temp file named by `$TV_JOBS` and the source just `cat`s that file; opened any other way the list is empty. Entries are `%<id> <state> <command><TAB><id><TAB><pgid>`; <kbd>enter</kbd> prints the id and the binding runs `fg %<id>`.
|
||||
|
||||
The preview finds the job's processes by process group (procps `ps -g` matches sessions, so it filters `ps -e -o pgid,pid`) and shows the first process's cwd and start time, then `ps --forest` with PID, STAT (`T` = stopped), elapsed time, CPU, RSS, and arguments. The preview script avoids `{`/`}` because tv would parse them as templates and leave `{split:…}` unexpanded.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>enter</kbd> | Print the job id; `_jobs_bind` runs `fg` on it |
|
||||
| <kbd>ctrl-d</kbd> | `kill` then `reload_source`: SIGTERM to the job's process group, then SIGCONT so a stopped job receives it. Once the group is gone (polled up to 1 s) the row is removed from `$TV_JOBS`, so the reloaded list drops it; a job that ignores SIGTERM stays listed. Fish reports the termination at the next prompt |
|
||||
|
||||
**Needs:** `ps`, fish.
|
||||
|
||||
### kitty-days
|
||||
|
||||
Lists the daily kitty sessions that `~/.config/kitty/day_session.py` saves under `~/.local/share/kitty-sessions/<monday>/<day>.kitty-session` ([kitty daily sessions](../terminal/kitty.md#daily-sessions)). Entries are `<day> <weekday> week of <monday> <n> tabs<TAB><session file>`, through `mtime-sort`. The preview parses the session file with an inline `python3` script (written without braces, which tv would treat as templates): each tab, then each window's cwd and the program it restores, with the last 6 lines of its saved scrollback in color.
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| <kbd>enter</kbd> | `open`: `setsid -f kitty --session <file>`, a new kitty with that day's tabs, scrollback and programs. Past days are read-only snapshots; today's file comes back as the live day if no kitty owns it |
|
||||
| <kbd>ctrl-e</kbd> | `edit`: the session file in `nvim` |
|
||||
|
||||
**Needs:** `kitty`, `python3`, `mtime-sort`.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Topgrade
|
||||
|
||||
Topgrade config for the Arch/CachyOS machines, with paru as the package manager, chezmoi re-add before upgrades, and post-upgrade hooks.
|
||||
|
||||
[Topgrade](https://github.com/topgrade-rs/topgrade) runs every updater on the machine in one pass: system packages, cargo, firmware, git repos, flatpak, and custom commands. The config is written for a CachyOS (Arch) box running fish; most keys are left at topgrade's defaults.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | `dot_config/topgrade.toml` |
|
||||
| Target | `~/.config/topgrade.toml` |
|
||||
| Packages | `topgrade` (AUR, via yay or paru) |
|
||||
|
||||
> [!NOTE]
|
||||
> The file's comments say topgrade on the owner's machine is cargo-installed, and that `cargo install-update` keeps it current. That is why self-update is off.
|
||||
|
||||
## [misc]
|
||||
|
||||
| Key | Value | Why |
|
||||
| --- | --- | --- |
|
||||
| `pre_sudo` | `true` | Caches sudo credentials up front so an unattended run does not stop at a password prompt partway through. |
|
||||
| `no_self_update` | `true` | The `[cargo]` step already updates the topgrade binary; two updaters would fight over it. |
|
||||
| `cleanup` | `true` | Runs distro cleanup afterwards (paccache needs `pacman-contrib`; skipped if absent). |
|
||||
| `show_distribution_summary` | `true` | Surfaces `*.pacnew` / `*.pacsave` files to merge. |
|
||||
| `disable` | `["containers", "config_update"]` | Skips pulling `:latest` for every local container image, and skips topgrade's config-update step. |
|
||||
| `ignore_failures` | `["uv", "containers"]` | Failures in these steps do not fail the run. |
|
||||
|
||||
The Python pip/conda steps stay off (topgrade's default), which protects a pinned ROCm build of torch from blind upgrades. A `remote_topgrades` entry for another host is present but commented out.
|
||||
|
||||
## [linux]
|
||||
|
||||
| Key | Value | Why |
|
||||
| --- | --- | --- |
|
||||
| `arch_package_manager` | `"paru"` | Pins the Arch backend so topgrade does not fall back to bare `pacman`, which would skip the AUR. |
|
||||
| `show_arch_news` | `true` | Prints archlinux.org news before upgrading; CachyOS tracks Arch, so manual-intervention notices still apply. |
|
||||
|
||||
The comments around `arch_package_manager` describe shelly (the CachyOS package CLI) as the intended value, but the active setting is `paru`. shelly still runs as a post-command (below). A commented `shelly_arguments` line is kept for reference.
|
||||
|
||||
## Other steps
|
||||
|
||||
| Section | Key | Value | Effect |
|
||||
| --- | --- | --- | --- |
|
||||
| `[cargo]` | `git` | `true` | Also updates git-sourced cargo installs, not only crates.io ones. Needs `cargo-update`. |
|
||||
| `[firmware]` | `upgrade` | `false` | fwupd updates are listed, not flashed, so firmware is reviewed before applying. |
|
||||
| `[git]` | (none active) | | Predefined repos are still pulled; custom `repos` and `arguments` are commented out to avoid tripping over dirty working trees. |
|
||||
| `[flatpak]` | (none active) | | User-scope flatpaks update without sudo; `use_sudo` is commented out. |
|
||||
|
||||
## Pre- and post-commands
|
||||
|
||||
Pre-commands run before any upgrade step:
|
||||
|
||||
| Name | Command | Purpose |
|
||||
| --- | --- | --- |
|
||||
| Chezmoi Re-Add | `chezmoi re-add` | Pulls local edits to managed dotfiles back into the chezmoi source repo before anything changes them. Only re-adds files chezmoi already manages; review and commit in `~/.local/share/chezmoi` afterwards. |
|
||||
| GHQ Update | see below | Updates every ghq-managed repo. |
|
||||
|
||||
**~/.config/topgrade.toml**
|
||||
|
||||
```toml
|
||||
[pre_commands]
|
||||
"Chezmoi Re-Add" = "chezmoi re-add"
|
||||
"GHQ Update" = "ghq list | ghq get -u -s"
|
||||
```
|
||||
|
||||
Post-commands run after everything else:
|
||||
|
||||
| Name | Command |
|
||||
| --- | --- |
|
||||
| Hermes Update | `hermes update --backup --yes` |
|
||||
| Herdr Update | `herdr update` |
|
||||
| Shelly Update | `shelly upgrade-all -n` |
|
||||
| Cachy Update | `cachy-update` |
|
||||
|
||||
`shelly upgrade-all -n` is also available as the `sua` alias. The post-commands assume CachyOS tooling (`shelly`, `cachy-update`) and the owner's own CLIs (`hermes`, `herdr`); on machines without them those steps fail. omp has no post-command: it is a global mise tool, so topgrade's built-in mise step (`mise upgrade`) updates it, and `omp update` would overwrite the binary inside mise's install directory.
|
||||
|
||||
> [!NOTE]
|
||||
> `chezmoi re-add` does not overwrite templates, so edits made directly to rendered files such as `~/.gitconfig` are not captured; change the `.tmpl` source instead.
|
||||
@@ -0,0 +1,262 @@
|
||||
# Yazi
|
||||
|
||||
Yazi file manager config — layout, openers, bat previews, keymap, plugins, flavors, and the Neovim bridge.
|
||||
|
||||
[Yazi](https://yazi-rs.github.io) is a terminal file manager. It is the default way to browse files here: shell keybindings, aliases, and a kitty shortcut all open it, and it hands files to Neovim.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Source | [prdlk/yazi](https://github.com/prdlk/yazi) via `.chezmoiexternals/yazi.toml` (`git-repo`, pulled `--ff-only` every 168h) |
|
||||
| Target | `~/.config/yazi/` (git checkout; edit in place, commit and push from there) |
|
||||
| Files | `yazi.toml`, `keymap.toml`, `init.lua`, `package.toml`, `theme.toml`, `vfs.toml`, `flavors/{cyberdream,ii-auto}.yazi/flavor.toml`, `plugins/bunny.yazi/` (vendored) |
|
||||
| Helper | `dot_local/bin/executable_yazi-nvim-open` → `~/.local/bin/yazi-nvim-open` |
|
||||
| Packages | `yazi` plus preview backends `ffmpeg`, `ffmpegthumbnailer`, `poppler`, `resvg`, `imagemagick`, `7zip`, `unzip` |
|
||||
| Plugins | `run_onchange_after_install-yazi-plugins.sh.tmpl` → `ya pkg install` |
|
||||
|
||||
The whole directory is a symlink into the source tree, so edits apply without `chezmoi apply`.
|
||||
|
||||
```text
|
||||
- yazi/
|
||||
- yazi.toml
|
||||
- keymap.toml
|
||||
- init.lua
|
||||
- package.toml
|
||||
- theme.toml
|
||||
- vfs.toml
|
||||
- flavors/
|
||||
- cyberdream.yazi/
|
||||
- flavor.toml
|
||||
- ii-auto.yazi/
|
||||
- flavor.toml
|
||||
- plugins/
|
||||
- bunny.yazi/
|
||||
```
|
||||
|
||||
## yazi.toml
|
||||
|
||||
Plain TOML, not a template.
|
||||
|
||||
**~/.config/yazi/yazi.toml**
|
||||
|
||||
```toml
|
||||
[mgr]
|
||||
ratio = [0, 2, 6]
|
||||
|
||||
[opener]
|
||||
play = [
|
||||
{ run = "mpv %s", orphan = true, for = "unix" },
|
||||
]
|
||||
edit = [
|
||||
{ run = 'yazi-nvim-open %s', block = true, for = "unix" },
|
||||
]
|
||||
open = [
|
||||
{ run = "xdg-open %s1", desc = "Open" },
|
||||
]
|
||||
|
||||
[open]
|
||||
prepend_rules = [
|
||||
{ url = "*.{md,markdown}", use = [ "fry", "edit", "open" ] },
|
||||
]
|
||||
|
||||
[[plugin.prepend_previewers]]
|
||||
mime = "text/*"
|
||||
run = 'piper -- bat -p --color=always --terminal-width=$w "$1"'
|
||||
|
||||
[[plugin.prepend_previewers]]
|
||||
mime = "application/{mbox,javascript,wine-extension-ini}"
|
||||
run = 'piper -- bat -p --color=always --terminal-width=$w "$1"'
|
||||
```
|
||||
|
||||
| Setting | Effect |
|
||||
| --- | --- |
|
||||
| `mgr.ratio = [0, 2, 6]` | Hides the parent column; the file list takes 2/8 of the width and the preview 6/8. |
|
||||
| `opener.play` | `mpv`, detached from yazi (`orphan = true`). |
|
||||
| `opener.edit` | `yazi-nvim-open`, blocking, so yazi waits for the editor. |
|
||||
| `opener.open` | `xdg-open` on the first selected file. |
|
||||
| `open.prepend_rules` | Markdown files list `fry`, then `edit`, then `open`. No `fry` opener is defined in this file. |
|
||||
| Previewers | Text MIME types, plus mbox, JavaScript, and wine `.ini`, preview through the `piper` plugin running `bat -p --color=always`, so previews use the [bat](bat.md) cyberdream theme. Markdown falls under `text/*` too. |
|
||||
|
||||
## Keymap
|
||||
|
||||
All bindings in `keymap.toml` are `[[mgr.prepend_keymap]]` entries, so they take priority over yazi's defaults and leave the rest of the default keymap intact.
|
||||
|
||||
### Navigation
|
||||
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| <kbd>l</kbd> | `plugin smart-enter`: enter a directory, or open a file |
|
||||
| <kbd>g</kbd> <kbd>r</kbd> | cd to the git root (`git rev-parse --show-toplevel`) |
|
||||
| <kbd>g</kbd> <kbd>C</kbd> | cd to `~/.local/share/chezmoi` |
|
||||
| <kbd>g</kbd> <kbd>d</kbd> | cd to `~/Downloads` |
|
||||
| <kbd>g</kbd> <kbd>D</kbd> <kbd>d</kbd> | cd to `$GHQ_ROOT/github.com` |
|
||||
| <kbd>g</kbd> <kbd>D</kbd> <kbd>w</kbd> | cd to `$GHQ_ROOT/github.com/wltbs` |
|
||||
| <kbd>g</kbd> <kbd>D</kbd> <kbd>p</kbd> | cd to `$GHQ_ROOT/github.com/prdlk` |
|
||||
| <kbd>g</kbd> <kbd>l</kbd> | cd to `~/.local` |
|
||||
| <kbd>g</kbd> <kbd>p</kbd> | cd to `~/Pictures` |
|
||||
| <kbd>g</kbd> <kbd>o</kbd> <kbd>p</kbd> | cd to `~/Documents/Obsidian/Personal` |
|
||||
| <kbd>g</kbd> <kbd>o</kbd> <kbd>r</kbd> | cd to `~/Documents/Obsidian/Research` |
|
||||
| <kbd>g</kbd> <kbd>o</kbd> <kbd>w</kbd> | cd to `~/Documents/Obsidian/Walletbase` |
|
||||
| <kbd>g</kbd> <kbd>b</kbd> | Back to the previous directory |
|
||||
| <kbd>g</kbd> <kbd>f</kbd> | Forward to the next directory |
|
||||
| <kbd>g</kbd> <kbd>m</kbd> <kbd>p</kbd> | cd to `sftp://gdrive-personal` |
|
||||
| <kbd>g</kbd> <kbd>m</kbd> <kbd>s</kbd> | cd to `sftp://gdrive-sonr` |
|
||||
| <kbd>g</kbd> <kbd>m</kbd> <kbd>h</kbd> | cd to `sftp://gdrive-hyperauth` |
|
||||
| <kbd>;</kbd> | Open the bunny hop menu |
|
||||
| <kbd>'</kbd> | Bunny fuzzy search over hops |
|
||||
|
||||
`$GHQ_ROOT` is `~/Code`, exported in `dot_config/fish/conf.d/10-env.fish`, `dot_zshrc`, and `dot_bashrc`. The `g m` bindings need the SFTP services in `vfs.toml`, which are commented out (see [vfs.toml](#vfstoml)).
|
||||
|
||||
### Finding and opening
|
||||
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| <kbd>F</kbd> | `plugin smart-filter`: live filter; enters the directory or opens the file when one match remains |
|
||||
| <kbd>Ctrl</kbd>+<kbd>t</kbd> | `plugin tv`: jump to a file with television |
|
||||
| <kbd>Ctrl</kbd>+<kbd>g</kbd> | `plugin tv ghq`: jump to a ghq repo |
|
||||
| <kbd>Ctrl</kbd>+<kbd>d</kbd> | `plugin tv dirs`: jump to a directory |
|
||||
| <kbd>Ctrl</kbd>+<kbd>f</kbd> | `plugin tv text`: search text and open the match in Neovim at that line |
|
||||
| <kbd>Ctrl</kbd>+<kbd>v</kbd> | `yazi-nvim-open vsplit` on the selection (inside an nvim terminal only) |
|
||||
| <kbd>Ctrl</kbd>+<kbd>x</kbd> | `yazi-nvim-open split` on the selection (inside an nvim terminal only) |
|
||||
|
||||
The `tv` plugin shells out to [television](https://github.com/alexpasmantier/television), which is not in `packages.yaml`.
|
||||
|
||||
### Files and session
|
||||
|
||||
| Keys | Action |
|
||||
| --- | --- |
|
||||
| <kbd>A</kbd> | Create a directory (`create --dir`) |
|
||||
| <kbd>N</kbd> | Bulk-create files (`bulk_create`) |
|
||||
| <kbd>Shift</kbd>+<kbd>Tab</kbd> | Drop into `$SHELL` in the current directory (blocking) |
|
||||
| <kbd>Ctrl</kbd>+<kbd>q</kbd> | Quit |
|
||||
|
||||
In the shell opened by <kbd>Shift</kbd>+<kbd>Tab</kbd>, the `q` function (fish, bash, zsh) runs `ya emit quit` when `$YAZI_ID` is set, then exits. That closes the parent yazi too instead of returning to it.
|
||||
|
||||
## Plugins
|
||||
|
||||
`package.toml` pins every plugin to a commit and hash. `~/.config/yazi` is the [prdlk/yazi](https://github.com/prdlk/yazi) checkout, so `ya pkg install` writes plugins into its `plugins/`, which that repo's `.gitignore` excludes (`plugins/*`, `!plugins/bunny.yazi/`). The post-apply script reinstalls from the manifest:
|
||||
|
||||
**run_onchange_after_install-yazi-plugins.sh.tmpl**
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
# Install the yazi plugins pinned in ~/.config/yazi/package.toml whenever it
|
||||
# changes. ~/.config/yazi is a git-repo external (prdlk/yazi); plugins land in
|
||||
# its plugins/ dir (gitignored there, except the vendored bunny.yazi).
|
||||
{{- $pkg := joinPath .chezmoi.homeDir ".config/yazi/package.toml" }}
|
||||
# package.toml hash: {{ if stat $pkg }}{{ include $pkg | sha256sum }}{{ end }}
|
||||
set -eu
|
||||
command -v ya >/dev/null 2>&1 || exit 0
|
||||
[ -f "${XDG_CONFIG_HOME:-$HOME/.config}/yazi/package.toml" ] || exit 0
|
||||
# ya's clone cache is disposable and clones made by an older ya can break a
|
||||
# newer one, so rebuild it once before giving up.
|
||||
ya pkg install || {
|
||||
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/yazi/packages"
|
||||
ya pkg install
|
||||
}
|
||||
```
|
||||
|
||||
The embedded hash makes chezmoi re-run the script whenever `package.toml` changes.
|
||||
|
||||
bunny is not in `package.toml`: it is vendored at `71b14a3` in `plugins/bunny.yazi/` of the yazi repo, because its repo contains a Git symlink to a directory (`test/plugins/bunny.yazi -> ../..`) that `ya pkg` cannot materialize. To update it, re-copy the files from upstream.
|
||||
|
||||
| Plugin | Rev | Used by |
|
||||
| --- | --- | --- |
|
||||
| `yazi-rs/plugins:smart-filter` | `efa4d79` | <kbd>F</kbd> |
|
||||
| `uhs-robert/sshfs` | `a8b8903` | `require("sshfs"):setup()` in `init.lua` |
|
||||
| `yazi-rs/plugins:smart-enter` | `efa4d79` | <kbd>l</kbd> |
|
||||
| `yazi-rs/plugins:piper` | `efa4d79` | bat previewers in `yazi.toml` |
|
||||
| `Rolv-Apneseth/starship` | `ea92cf4` | Installed; not set up in `init.lua` |
|
||||
| `mikavilpas/easyjump.yazi:easyjump` | `4aa1a06` | Installed; not bound in `keymap.toml` |
|
||||
| `cap153/tv` | `b6b1f12` | <kbd>Ctrl</kbd>+<kbd>t</kbd>/<kbd>g</kbd>/<kbd>d</kbd>/<kbd>f</kbd> |
|
||||
| `stelcodes/bunny` | `71b14a3` (vendored) | <kbd>;</kbd> and <kbd>'</kbd>, configured in `init.lua` |
|
||||
| `coder0x6675/osc7` | `ae73046` | Installed; not referenced in config |
|
||||
| `WhoSowSee/mdv-previewer` | `ea4732b` | Installed; not referenced in `yazi.toml` |
|
||||
| `yazi-rs/plugins:vcs-files` | `efa4d79` | Installed; not bound in `keymap.toml` |
|
||||
| `yazi-rs/plugins:git` | `efa4d79` | `require("git"):setup { order = 1500 }` |
|
||||
| `yazi-rs/plugins:no-status` | `efa4d79` | `require("no-status"):setup()` |
|
||||
|
||||
`[flavor] deps` is empty: flavors ship as files in `flavors/`, not as packages.
|
||||
|
||||
### init.lua
|
||||
|
||||
**~/.config/yazi/init.lua**
|
||||
|
||||
```lua
|
||||
require("sshfs"):setup()
|
||||
require("bunny"):setup({ hops = { ... }, desc_strategy = "path", ephemeral = true, tabs = true, notify = false, fuzzy_cmd = "fzf" })
|
||||
require("git"):setup { order = 1500 }
|
||||
require("no-status"):setup()
|
||||
```
|
||||
|
||||
- **sshfs**: default setup, for mounting remote hosts over SSHFS.
|
||||
- **git**: git status signs in the file list, ordered at `1500` in the linemode.
|
||||
- **no-status**: removes the status bar.
|
||||
- **bunny**: bookmark "hops", with ephemeral hops and tab hops on, no notification after hopping, `fzf` for fuzzy search, and the path as the label when a hop has no `desc`.
|
||||
|
||||
| Hop key | Path | Label |
|
||||
| --- | --- | --- |
|
||||
| <kbd>/</kbd> | `/` | |
|
||||
| <kbd>t</kbd> | `/tmp` | |
|
||||
| <kbd>~</kbd> | `~` | Home |
|
||||
| <kbd>m</kbd> | `~/Music` | Music |
|
||||
| <kbd>d</kbd> | `~/Desktop` | Desktop |
|
||||
| <kbd>D</kbd> | `~/Developer` | Developer |
|
||||
| <kbd>c</kbd> | `~/.config` | Config files |
|
||||
| <kbd>l</kbd> <kbd>s</kbd> | `~/.local/share` | Local share |
|
||||
| <kbd>l</kbd> <kbd>b</kbd> | `~/.local/bin` | Local bin |
|
||||
| <kbd>l</kbd> <kbd>t</kbd> | `~/.local/state` | Local state |
|
||||
|
||||
## Theme and flavors
|
||||
|
||||
**~/.config/yazi/theme.toml**
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
light = "cyberdream"
|
||||
dark = "cyberdream"
|
||||
use = "cyberdream"
|
||||
```
|
||||
|
||||
Cyberdream is active in both light and dark mode, the palette shared with Ghostty, tmux, nvim, and television. Two flavors ship in `flavors/`:
|
||||
|
||||
| Flavor | Origin | Notes |
|
||||
| --- | --- | --- |
|
||||
| `cyberdream.yazi` | Hand-maintained cyberdream palette | Active. Sets `syntect_theme = "~/.config/bat/themes/cyberdream.tmTheme"`, so yazi's built-in code preview shares the [bat](bat.md) theme. Cyan cwd (`#5ef1ff`), green find keyword (`#5eff6c`), yellow and red copy/cut markers. |
|
||||
| `ii-auto.yazi` | Generated by the ii wallpaper theming system ("Do not edit manually") | Not selected. Monochrome palette taken from the wallpaper. |
|
||||
|
||||
To switch, change `use` (and `light`/`dark`) in `theme.toml` to `ii-auto`.
|
||||
|
||||
### Icons
|
||||
|
||||
`theme.toml` also carries a large `[icon]` table:
|
||||
|
||||
- `prepend_dirs`: Nerd Font icons for named directories: `Applications`, `Code`, `Desktop`, `Developer`, `github.com`, `prdlk`, `wltbs`, `Shared`, `Templates`, `env`, `go`, `js`, `dart`, `py`, `rs`, `tex`, `md`, `docs`, `prad.codes`.
|
||||
- `files` and `exts`: several hundred filename and extension rules, from dotfiles like `.gitignore` and `.zshrc` to formats like `kicad_pcb` and `wrangler.toml`. Each rule sets a Nerd Font glyph and a foreground color from the Catppuccin Mocha palette (`#a6e3a1`, `#f38ba8`, `#89b4fa`, `#cba6f7`, `#fab387`, ...).
|
||||
|
||||
Glyphs need a Nerd Font. The terminal uses VictorMono Nerd Font (see [Terminal](../terminal/README.md)).
|
||||
|
||||
## vfs.toml
|
||||
|
||||
Every line is commented out. The file sketches three SFTP services (`gdrive-personal`, `gdrive-sonr`, `gdrive-hyperauth`) on local ports, meant to be served by a local SFTP bridge to Google Drive. Until they are uncommented and the bridge is running, the <kbd>g</kbd> <kbd>m</kbd> bindings have nothing to connect to.
|
||||
|
||||
## Neovim bridge: yazi-nvim-open
|
||||
|
||||
`~/.local/bin/yazi-nvim-open [vsplit|split|tabedit] file...` backs the `edit` opener and the <kbd>Ctrl</kbd>+<kbd>v</kbd>/<kbd>Ctrl</kbd>+<kbd>x</kbd> bindings. It lives on `PATH` because yazi does not expand `$HOME` in opener commands.
|
||||
|
||||
1. **Parse mode**: A first argument of `vsplit`, `split`, or `tabedit` selects the mode; anything else means `edit`.
|
||||
2. **Standalone yazi ($NVIM unset)**: `edit` runs `exec nvim "$@"`. Split modes do nothing and exit 0.
|
||||
3. **Inside an nvim terminal ($NVIM set)**: Each file is resolved with `realpath`, escaped for a Vimscript string, and sent to the host with `nvim --server "$NVIM" --remote-expr "v:lua.EditFromYazi(mode, path)"`. The nvim side opens the file and hides the yazi window.
|
||||
|
||||
Set `YAZI_OPEN_TRACE` to a file path to log each call's `$NVIM`, mode, and arguments.
|
||||
|
||||
## Launching yazi
|
||||
|
||||
| Entry point | Behavior |
|
||||
| --- | --- |
|
||||
| `y` / `yy` (fish functions) | Run yazi with `--cwd-file`, then `cd` to the directory yazi was in on exit. The two are identical apart from `y` calling `command rm`. |
|
||||
| `yy` (bash, zsh functions) | Same wrapper for bash and zsh. |
|
||||
| <kbd>Ctrl</kbd>+<kbd>e</kbd> / <kbd>Ctrl</kbd>+<kbd>f</kbd> | fish and zsh keybindings that run `yy` and repaint the prompt. |
|
||||
| <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>f</kbd> | [Kitty](../terminal/kitty.md): yazi in a new tab at the current directory. |
|
||||
| Aliases | `e.y`, `e.k`, `e.n`, `e.f`, `e.v` open config directories in yazi; `cdc`, `cdl`, `cdw`, and `,,` (fish only; chezmoi source) cd first, then start yazi. |
|
||||
|
||||
[tmux](../terminal/tmux.md) sets `allow-passthrough on` so yazi's kitty-protocol image previews work inside tmux. Kitty's tab bar and the tmux window-name map show a folder icon for yazi. See [aliases](../shell/aliases.md) and [fish](../shell/fish.md) for the shell side.
|
||||
Reference in new issue
Block a user