diff --git a/.gitignore b/.gitignore index ffe22b2..2da5536 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,8 @@ .config/btop .config/neofetch /.aider* -__pycache__/ \ No newline at end of file +home_config/nvim/lazy-lock.json +__pycache__/ +*.bak +# zellij web-pilot auth tokens — credentials, never commit +home_config/zellij/pilot-tokens/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..32ddf1b --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# dotfiles + +Personal dotfiles. Configs are organized by where they get linked: + +| Folder | Links into | Examples | +| -------------- | ------------ | --------------------------------- | +| `home_root` | `~` | `.zshrc`, `.wezterm.lua`, `.nanorc` | +| `home_config` | `~/.config` | `nvim`, `zellij`, `ghostty`, `aerospace` | +| `windows` | Windows-only | `windows-terminal/settings.json` | +| `lk-installers`| n/a | helper scripts to install tooling | + +## Linux / macOS (GNU stow) + +One-shot, host-aware: + +```bash +./link-dotfiles # link only (installs stow if missing) +./link-dotfiles --apps # fresh box: install all CLI tools, then link +``` + +It detects the host (macOS vs Linux), installs [`stow`](https://www.gnu.org/software/stow/) +if missing (Homebrew on macOS; apt/dnf/pacman on Linux), then runs +`stow home_root` and `stow --target=$HOME/.config home_config`. Re-running is +safe — stow is idempotent. + +### Installing the tools + +The configs assume a handful of CLI tools (`bat`, `fd`, `fzf`, `zoxide`, +`zellij`, `nvim`, `btop`, `htop`, `tldr`, `fastfetch`, plus the `fzf-git.sh` +helper). One host-aware installer covers them: + +```bash +./lk-installers/install-apps # macOS -> Homebrew; Linux -> apt + upstream +``` + +On macOS it bootstraps Homebrew if absent. It's idempotent — re-running only +fills gaps. `./link-dotfiles --apps` just runs this first, then links. + +The zsh prompt and plugins (zinit, Powerlevel10k, autosuggestions, +syntax-highlighting, completions, vi-mode, fzf-tab) **self-install** from +`.zshrc` on first shell start, so they're not part of `install-apps`. + +## Windows (PowerShell) + +`stow` doesn't run natively on Windows, so use the PowerShell equivalent. It +mirrors stow by creating **junctions** for directories and **symlinks/hardlinks** +for files — none of which require administrator rights: + +```powershell +pwsh -File .\link-dotfiles.ps1 +``` + +- Re-running is safe: already-correct links are skipped. +- Any real file/dir that would be overwritten is moved aside to `.bak`. +- Preview without changing anything: `pwsh -File .\link-dotfiles.ps1 -DryRun` + +The repo stays the source of truth — edit files under `home_root` / +`home_config` and the linked locations update live. + +### Windows Terminal + +`windows/windows-terminal/settings.json` is linked into the Terminal package +folder (`...\Microsoft.WindowsTerminal_*\LocalState\settings.json`) by +`link-dotfiles.ps1`. Because it's linked, changes you make in the Terminal UI +write straight back into this repo. The settings include the **Australis** +color scheme and the **VictorMono Nerd Font Mono** profile font; install the +font (see below) so glyphs render. + +## Prompt + +**zsh uses [Powerlevel10k](https://github.com/romkatv/powerlevel10k)** — loaded +through zinit straight from `.zshrc`, with the instant-prompt preamble near the +top for fast startup. The theme config lives at `home_root/.p10k.zsh` (stowed to +`~/.p10k.zsh`); there's no separate installer — zinit fetches it on first shell +start. + +PowerShell uses [starship](https://starship.rs/) (configured separately). + +Both expect a **Nerd Font v3** (e.g. **VictorMono Nerd Font Mono**) installed and +selected in your terminal for glyphs to render. diff --git a/home_config/ghostty/config b/home_config/ghostty/config index e8d4a54..4f7d1af 100644 --- a/home_config/ghostty/config +++ b/home_config/ghostty/config @@ -1,38 +1,53 @@ -# Font -font-family = VictorMono Nerd Font -font-size = 16 -font-style = Regular -font-style-bold = Bold -font-style-italic = Italic -font-style-bold-italic = Bold Italic -adjust-cell-height = 5% +# Ghostty configuration +# Docs: https://ghostty.org/docs/config +# Reload in-app without restarting: Cmd+Shift+, + +# --- Font --------------------------------------------------------------- +# VictorMono Nerd Font (installed in ~/Library/Fonts). The "Mono" variant +# keeps glyphs strictly fixed-width, which is what you want in a terminal. +font-family = VictorMono Nerd Font Mono +font-size = 14 +# Slightly heavier strokes render nicer on macOS Retina displays. font-thicken = true -# Theme +# Victor Mono ships a gorgeous cursive italic — make sure it's used. +font-style-italic = Italic + +# --- Theme -------------------------------------------------------------- +# Australis (Aurora Australis) — a dark theme. Defined in +# ~/.config/ghostty/themes/australis (ported from the upstream WezTerm config). theme = australis -# Window -window-padding-x = 10 -window-padding-y = 10 -window-padding-balance = true -background-opacity = 0.95 -background-blur-radius = 20 -macos-titlebar-style = tabs - -# Cursor -cursor-style = bar -cursor-style-blink = true - -# Behavior -scrollback-limit = 10000 +# --- Cursor ------------------------------------------------------------- +cursor-style = block +cursor-style-blink = false mouse-hide-while-typing = true -copy-on-select = false -confirm-close-surface = false -# Shell integration +# --- Window ------------------------------------------------------------- +window-padding-x = 8 +window-padding-y = 6 +# Keep padding color matched to the background for a clean look. +window-padding-balance = true +# Reopen with the same windows/tabs you left. +window-save-state = always +macos-titlebar-style = tabs +background-opacity = 0.97 + +# --- Clipboard / selection --------------------------------------------- +copy-on-select = clipboard +clipboard-trim-trailing-spaces = true + +# --- macOS / shell behavior -------------------------------------------- +# Let the left Option key send Alt (so Alt+f, Alt+b, etc. work in shells/ +# editors); keep right Option for composing accented characters. +macos-option-as-alt = left shell-integration = detect -shell-integration-features = cursor,sudo,title +# ssh-env + ssh-terminfo: on connect, propagate a compatible TERM and +# auto-install Ghostty's terminfo entry on the remote host (v1.2.0+), so +# remote apps render correctly without "unknown terminal type" errors. +shell-integration-features = cursor,sudo,title,ssh-env,ssh-terminfo -# Rendering -bold-is-bright = false -minimum-contrast = 1.1 +# --- Misc --------------------------------------------------------------- +# Only prompt to close when a process is still running. +confirm-close-surface = true +scrollback-limit = 10000000 diff --git a/home_config/nvim/lazy-lock.json b/home_config/nvim/lazy-lock.json index e2922f7..ba82391 100644 --- a/home_config/nvim/lazy-lock.json +++ b/home_config/nvim/lazy-lock.json @@ -1,13 +1,22 @@ { +<<<<<<< HEAD + "LazyVim": { "branch": "main", "commit": "3f034d0a7f58031123300309f2efd3bb0356ee21" }, + "blink.cmp": { "branch": "main", "commit": "4607923f60029f8cb28e6950a078f93fd33e1288" }, +======= "LazyVim": { "branch": "main", "commit": "ec5981dfb1222c3bf246d9bcaa713d5cfa486fbd" }, "blink.cmp": { "branch": "main", "commit": "49f211fe5d729df53df4c042d7c3464cf47d199e" }, +>>>>>>> 00717500056db846954b09bcfac4ebb3f1aa84e6 "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "catppuccin": { "branch": "main", "commit": "5b5e3aef9ad7af84f463d17b5479f06b87d5c429" }, "conform.nvim": { "branch": "master", "commit": "f9ef25a7ef00267b7d13bfc00b0dea22d78702d5" }, "flash.nvim": { "branch": "main", "commit": "3c942666f115e2811e959eabbdd361a025db8b63" }, "friendly-snippets": { "branch": "main", "commit": "efff286dd74c22f731cdec26a70b46e5b203c619" }, "gitsigns.nvim": { "branch": "main", "commit": "7010000889bfb6c26065e0b0f7f1e6aa9163edd9" }, +<<<<<<< HEAD + "grug-far.nvim": { "branch": "main", "commit": "2c7ef09353714bd4954f5459d9a7e2579a050dc7" }, +======= "grug-far.nvim": { "branch": "main", "commit": "5da116bc99a03e14be3b824e319f4e49c676af78" }, +>>>>>>> 00717500056db846954b09bcfac4ebb3f1aa84e6 "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, "lazydev.nvim": { "branch": "main", "commit": "2367a6c0a01eb9edb0464731cc0fb61ed9ab9d2c" }, "lualine.nvim": { "branch": "master", "commit": "9fef261b53fbe3a2ef01ee9667f6fde064b1ed10" }, @@ -15,12 +24,21 @@ "mason.nvim": { "branch": "main", "commit": "fc98833b6da5de5a9c5b1446ac541577059555be" }, "mini.ai": { "branch": "main", "commit": "978ffc65c6b513fde9ef075326d34d89197f1ea5" }, "mini.icons": { "branch": "main", "commit": "ec61af6e606fc89ee3b1d8f2f20166a3ca917a36" }, +<<<<<<< HEAD + "mini.pairs": { "branch": "main", "commit": "cd9d95e33645781f86ca5ffe0a6885a22d59f127" }, + "noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, + "nui.nvim": { "branch": "main", "commit": "8d3bce9764e627b62b07424e0df77f680d47ffdb" }, + "nvim-lint": { "branch": "master", "commit": "936197073214c26a347fb933c9459c8766376b23" }, + "nvim-lspconfig": { "branch": "master", "commit": "fb733ac734249ccf293e5c8018981d4d8f59fa8f" }, + "nvim-treesitter": { "branch": "master", "commit": "d26ce0126694283cf32bf1f4192ef37ef404c037" }, +======= "mini.pairs": { "branch": "main", "commit": "b90e36aa5ca5e0d825e77ad67aac22214a4d9096" }, "noice.nvim": { "branch": "main", "commit": "0427460c2d7f673ad60eb02b35f5e9926cf67c59" }, "nui.nvim": { "branch": "main", "commit": "8d3bce9764e627b62b07424e0df77f680d47ffdb" }, "nvim-lint": { "branch": "master", "commit": "93b8040115c9114dac1047311763bef275e752dc" }, "nvim-lspconfig": { "branch": "master", "commit": "401e50fae626c4707af12114b5ddb7bb9f4236a4" }, "nvim-treesitter": { "branch": "master", "commit": "85168f15808d89b0222313652b9d2777eda3cb08" }, +>>>>>>> 00717500056db846954b09bcfac4ebb3f1aa84e6 "nvim-treesitter-textobjects": { "branch": "master", "commit": "9937e5e356e5b227ec56d83d0a9d0a0f6bc9cad4" }, "nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" }, "persistence.nvim": { "branch": "main", "commit": "166a79a55bfa7a4db3e26fc031b4d92af71d0b51" }, diff --git a/home_config/ohmyposh/base.yaml b/home_config/ohmyposh/base.yaml deleted file mode 100644 index 3c4d2b0..0000000 --- a/home_config/ohmyposh/base.yaml +++ /dev/null @@ -1,136 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json - -version: 2 -final_space: true -console_title_template: "{{ .Shell }} in {{ .Folder }}" -blocks: -- type: prompt - alignment: left - segments: - - type: session - style: diamond - foreground: p:black - background: p:yellow - leading_diamond:  - trailing_diamond:  - template: " {{ if .SSHSession }}\ueba9 {{ end }}{{ .UserName }} " - - type: path - style: powerline - powerline_symbol:  - foreground: p:white - background: p:orange - template: " \uea83 {{ path .Path .Location }} " - properties: - style: folder - - type: git - style: powerline - powerline_symbol:  - foreground: p:black - foreground_templates: - - "{{ if or (.Working.Changed) (.Staging.Changed) }}p:black{{ end }}" - - "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:white{{ end }}" - - "{{ if gt .Ahead 0 }}p:white{{ end }}" - background: p:green - background_templates: - - "{{ if or (.Working.Changed) (.Staging.Changed) }}p:yellow{{ end }}" - - "{{ if and (gt .Ahead 0) (gt .Behind 0) }}p:red{{ end }}" - - "{{ if gt .Ahead 0 }}#49416D{{ end }}" - - "{{ if gt .Behind 0 }}#7A306C{{ end }}" - template: " {{ if .UpstreamURL }}{{ url .UpstreamIcon .UpstreamURL }} {{ end }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }} " - properties: - branch_max_length: 25 - fetch_status: true - fetch_upstream_icon: true - - type: root - style: powerline - powerline_symbol:  - foreground: p:white - background: p:yellow - template: " \uf0e7 " - - type: status - style: diamond - foreground: p:white - background: p:blue - background_templates: - - "{{ if gt .Code 0 }}p:red{{ end }}" - leading_diamond:  - trailing_diamond:  - template: " {{ if gt .Code 0 }}\uf00d{{ else }}\uf00c{{ end }} " - properties: - always_enabled: true -- type: rprompt - segments: - - type: node - style: plain - foreground: p:green - background: transparent - template: "\ue718 " - properties: - display_mode: files - fetch_package_manager: false - home_enabled: false - - type: go - style: plain - foreground: p:blue - background: transparent - template: "\ue626 " - properties: - fetch_version: false - - type: python - style: plain - foreground: p:yellow - background: transparent - template: "\ue235 " - properties: - display_mode: files - fetch_version: false - fetch_virtual_env: false - - type: shell - style: plain - foreground: p:white - background: transparent - template: "in {{ .Name }} " - - type: time - style: plain - foreground: p:white - background: transparent - template: at {{ .CurrentDate | date "15:04:05" }} -tooltips: -- type: aws - tips: - - aws - style: diamond - foreground: p:white - background: p:orange - leading_diamond:  - trailing_diamond:  - template: " \ue7ad {{ .Profile }}{{ if .Region }}@{{ .Region }}{{ end }} " - properties: - display_default: true -- type: az - tips: - - az - style: diamond - foreground: p:white - background: p:blue - leading_diamond:  - trailing_diamond:  - template: " \uebd8 {{ .Name }} " - properties: - display_default: true -transient_prompt: - foreground: p:black - background: transparent - template: "\ue0b6<,p:yellow> {{ .Folder }} \ue0b0 " -secondary_prompt: - foreground: p:black - background: transparent - template: "\ue0b6<,p:yellow> > \ue0b0 " -palette: - black: "#262B44" - blue: "#4B95E9" - green: "#59C9A5" - orange: "#F07623" - red: "#D81E5B" - white: "#E0DEF4" - yellow: "#F3AE35" diff --git a/home_config/ohmyposh/lkraven.toml b/home_config/ohmyposh/lkraven.toml deleted file mode 100644 index e6704bc..0000000 --- a/home_config/ohmyposh/lkraven.toml +++ /dev/null @@ -1,151 +0,0 @@ -#:schema https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json -console_title_template = '{{if .Root}}(Admin){{end}} {{.PWD}}' -version = 2 - -[palette] - black = '#222531' - bright_black = '#373b46' - blue = '#6388D8' - bright_blue = '#a4c4ff' - cyan = '#00b1a8' - bright_cyan = '#42dcd1' - green = '#16B866' - bright_green = '#51e08a' - magenta = '#9d78ff' - bright_magenta = '#d8adff' - red = '#ff491a' - bright_red = '#ff854f' - white = '#a9bcc3' - bright_white = '#cce7ec' - yellow = '#e1c631' - bright_yellow = '#FFE14E' - dark_30 = '#414751' - dark_40 = '#565f69' - dark_50 = '#6e7882' - dark_60 = '#86929d' - bright_70 = '#9daeb6' - bright_80 = '#b3cbcf' - bright_90 = '#cce7ec' - -[[blocks]] - alignment = 'left' - type = 'prompt' - - [[blocks.segments]] - type = 'os' - style = 'plain' - foreground = 'p:cyan' - template = '{{.Icon}} ' - - [[blocks.segments]] - foreground = 'p:dark_50' - style = 'plain' - template = '{{ .Path }} {{ if not .Writable}}󰌾 {{end}}' - type = 'path' - - [blocks.segments.properties] - style = 'agnoster' - -[[blocks]] - alignment = 'left' - type = 'prompt' - - [[blocks.segments]] - type = 'python' - foreground = 'p:blue' - background = "p:bright_black" - leading_diamond = "" - style = "diamond" - trailing_diamond = " " - template = "  {{ .Venv }} " - - [blocks.segments.properties] - display_default = false - - [[blocks.segments]] - foreground = 'p:bright_cyan' - background = "p:bright_black" - leading_diamond = "" - style = "diamond" - trailing_diamond = "" - template = " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }}  {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }}  {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }}  {{ .StashCount }}{{ end }} " - type = 'git' - - [blocks.segments.properties] - branch_icon = ' ' - commit_icon = ' ' - merge_icon = ' ' - no_commits_icon = ' ' - rebase_icon = ' ' - revert_icon = ' ' - tag_icon = ' ' - fetch_stash_count = true - fetch_status = true - fetch_upstream_icon = true - -[[blocks]] - type = "prompt" - alignment = "right" - - [[blocks.segments]] - foreground = 'p:green' - style = 'plain' - template = '{{ .FormattedMs }}' - type = 'executiontime' - - [blocks.segments.properties] - style = 'austin' - - [[blocks.segments]] - type = "time" - style = "plain" - foreground = "p:dark_30" - - [blocks.segments.properties] - time_format = "3:04:05pm" - - [[blocks.segments]] - type = "session" - style = "plain" - foreground = "p:dark_50" - template = "{{.UserName}}" - - [[blocks.segments]] - type = "session" - style = "plain" - foreground = "p:cyan" - template = " {{ if .SSHSession }}󰄙 {{ else }}@{{ end }} " - - [[blocks.segments]] - type = "session" - style = "plain" - foreground = "p:blue" - template = "{{.HostName}}" - -[[blocks]] - type = 'prompt' - alignment = 'left' - newline = true - - [[blocks.segments]] - type = 'text' - style = 'plain' - foreground_templates = [ - "{{if gt .Code 0}}p:red{{end}}", - "{{if eq .Code 0}}p:bright_green{{end}}", - ] - background = 'transparent' - template = '❯ ' - -[transient_prompt] - foreground_templates = [ - "{{if gt .Code 0}}p:red{{end}}", - "{{if eq .Code 0}}p:bright_green{{end}}", - ] - background = 'transparent' - template = '❯ ' - -[secondary_prompt] - foreground = 'p:bright_green' - background = 'transparent' - template = '❯❯ ' diff --git a/home_config/thefuck/__pycache__/settings.cpython-311.pyc b/home_config/thefuck/__pycache__/settings.cpython-311.pyc deleted file mode 100644 index e41c178..0000000 Binary files a/home_config/thefuck/__pycache__/settings.cpython-311.pyc and /dev/null differ diff --git a/home_config/thefuck/__pycache__/settings.cpython-313.pyc b/home_config/thefuck/__pycache__/settings.cpython-313.pyc deleted file mode 100644 index 7b48a22..0000000 Binary files a/home_config/thefuck/__pycache__/settings.cpython-313.pyc and /dev/null differ diff --git a/home_config/thefuck/settings.py b/home_config/thefuck/settings.py deleted file mode 100644 index 8478f6d..0000000 --- a/home_config/thefuck/settings.py +++ /dev/null @@ -1,26 +0,0 @@ -# The Fuck settings file -# -# The rules are defined as in the example bellow: -# -# rules = ['cd_parent', 'git_push', 'python_command', 'sudo'] -# -# The default values are as follows. Uncomment and change to fit your needs. -# See https://github.com/nvbn/thefuck#settings for more information. -# - -# rules = [] -# exclude_rules = [] -# wait_command = 3 -# require_confirmation = True -# no_colors = False -# debug = False -# priority = {} -# history_limit = None -# alter_history = True -# wait_slow_command = 15 -# slow_commands = ['lein', 'react-native', 'gradle', './gradlew', 'vagrant'] -# repeat = False -# instant_mode = False -# num_close_matches = 3 -# env = {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'} -# excluded_search_path_prefixes = [] diff --git a/home_config/zellij/config.kdl b/home_config/zellij/config.kdl index c8f70cb..d9ea728 100644 --- a/home_config/zellij/config.kdl +++ b/home_config/zellij/config.kdl @@ -1,5 +1,5 @@ // -// THIS FILE WAS AUTOGENERATED BY ZELLIJ, THE PREVIOUS FILE AT THIS LOCATION WAS COPIED TO: /home/lkraven/.config/zellij/config.kdl.bak +// THIS FILE WAS AUTOGENERATED BY ZELLIJ, THE PREVIOUS FILE AT THIS LOCATION WAS COPIED TO: /home/lkraven/.config/zellij/config.kdl.bak.1 // keybinds clear-defaults=true { @@ -247,6 +247,126 @@ keybinds clear-defaults=true { bind "z" { ToggleFocusFullscreen; SwitchToMode "normal"; } } } +themes { + australis { + text_unselected { + base 204 231 236 + background 65 71 81 + emphasis_0 164 196 255 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 66 220 209 + } + text_selected { + base 230 250 255 + background 99 136 216 + emphasis_0 255 225 78 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + ribbon_selected { + base 65 71 81 + background 99 136 216 + emphasis_0 81 224 138 + emphasis_1 22 184 102 + emphasis_2 225 198 49 + emphasis_3 255 133 79 + } + ribbon_unselected { + base 204 231 236 + background 86 95 105 + emphasis_0 81 224 138 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + table_title { + base 169 188 195 + background 34 37 49 + emphasis_0 255 133 79 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 73 26 + } + table_cell_selected { + base 204 231 236 + background 65 71 81 + emphasis_0 255 225 78 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + table_cell_unselected { + base 169 188 195 + background 34 37 49 + emphasis_0 255 73 26 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + list_selected { + base 204 231 236 + background 65 71 81 + emphasis_0 255 225 78 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + list_unselected { + base 169 188 195 + background 34 37 49 + emphasis_0 225 198 49 + emphasis_1 22 184 102 + emphasis_2 99 136 216 + emphasis_3 255 133 79 + } + frame_selected { + base 99 136 216 + background 34 37 49 + emphasis_0 255 73 26 + emphasis_1 22 184 102 + emphasis_2 225 198 49 + emphasis_3 255 133 79 + } + frame_highlight { + base 81 224 138 + background 34 37 49 + emphasis_0 255 133 79 + emphasis_1 99 136 216 + emphasis_2 225 198 49 + emphasis_3 255 73 26 + } + exit_code_success { + base 22 184 102 + background 0 + emphasis_0 255 73 26 + emphasis_1 99 136 216 + emphasis_2 225 198 49 + emphasis_3 255 133 79 + } + exit_code_error { + base 255 73 26 + background 0 + emphasis_0 22 184 102 + emphasis_1 99 136 216 + emphasis_2 225 198 49 + emphasis_3 255 133 79 + } + multiplayer_user_colors { + player_1 255 73 26 + player_2 255 133 79 + player_3 225 198 49 + player_4 255 225 78 + player_5 157 120 255 + player_6 216 173 255 + player_7 99 136 216 + player_8 0 177 168 + player_9 66 220 209 + player_10 164 196 255 + } + } +} // Plugin aliases - can be used to change the implementation of Zellij // changing these requires a restart to take effect @@ -270,141 +390,9 @@ plugins { // Plugins to load in the background when a new session starts // eg. "file:/path/to/my-plugin.wasm" // eg. "https://example.com/my-plugin.wasm" -themes { - australis { - - text_unselected { - base 204 231 236 // #CCE7EC - Bright text - background 65 71 81 // #414751 - Dark 30 - emphasis_0 164 196 255 // #A4C4FF - Bright blue - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 66 220 209 // #42DCD1 - Bright cyan (was 255 133 79 - Bright red) - } - - text_selected { - base 230 250 255 // #E6FAFF - Brighter white (was 204 231 236) - background 99 136 216 // #6388D8 - Blue - emphasis_0 255 225 78 // #FFE14E - Bright yellow - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - ribbon_unselected { - base 204 231 236 // #CCE7EC - Bright white - background 86 95 105 // #565f69 - Dark 40 (was 65 71 81 - Dark 30) - emphasis_0 81 224 138 // #51E08A - Bright green - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - ribbon_selected { - base 65 71 81 // #414751 - Dark 30 - background 99 136 216 // #6388D8 - Blue - emphasis_0 81 224 138 // #51E08A - Bright green - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 225 198 49 // #E1C631 - Yellow - emphasis_3 255 133 79 // #FF854F - Bright red - } - - table_title { - base 169 188 195 // #A9BCC3 - White - background 34 37 49 // #222531 - Black - emphasis_0 255 133 79 // #FF854F - Bright red - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 73 26 // #FF491A - Red - } - - table_cell_unselected { - base 169 188 195 // #A9BCC3 - White - background 34 37 49 // #222531 - Black - emphasis_0 255 73 26 // #FF491A - Red - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - table_cell_selected { - base 204 231 236 // #CCE7EC - Bright white - background 65 71 81 // #414751 - Dark 30 - emphasis_0 255 225 78 // #FFE14E - Bright yellow - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - list_unselected { - base 169 188 195 // #A9BCC3 - White - background 34 37 49 // #222531 - Black - emphasis_0 225 198 49 // #E1C631 - Yellow - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - list_selected { - base 204 231 236 // #CCE7EC - Bright white - background 65 71 81 // #414751 - Dark 30 - emphasis_0 255 225 78 // #FFE14E - Bright yellow - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 99 136 216 // #6388D8 - Blue - emphasis_3 255 133 79 // #FF854F - Bright red - } - - frame_selected { - base 99 136 216 // #6388D8 - Blue - background 34 37 49 // #222531 - Black - emphasis_0 255 73 26 // #FF491A - Red - emphasis_1 22 184 102 // #16B866 - Green - emphasis_2 225 198 49 // #E1C631 - Yellow - emphasis_3 255 133 79 // #FF854F - Bright red - } - - frame_highlight { - base 81 224 138 // #51E08A - Bright green - background 34 37 49 // #222531 - Black - emphasis_0 255 133 79 // #FF854F - Bright red - emphasis_1 99 136 216 // #6388D8 - Blue - emphasis_2 225 198 49 // #E1C631 - Yellow - emphasis_3 255 73 26 // #FF491A - Red - } - - exit_code_success { - base 22 184 102 // #16B866 - Green - emphasis_0 255 73 26 // #FF491A - Red - emphasis_1 99 136 216 // #6388D8 - Blue - emphasis_2 225 198 49 // #E1C631 - Yellow - emphasis_3 255 133 79 // #FF854F - Bright red - } - - exit_code_error { - base 255 73 26 // #FF491A - Red - emphasis_0 22 184 102 // #16B866 - Green - emphasis_1 99 136 216 // #6388D8 - Blue - emphasis_2 225 198 49 // #E1C631 - Yellow - emphasis_3 255 133 79 // #FF854F - Bright red - } - - multiplayer_user_colors { - player_1 255 73 26 // #FF491A - Red - player_2 255 133 79 // #FF854F - Bright red - player_3 225 198 49 // #E1C631 - Yellow - player_4 255 225 78 // #FFE14E - Bright yellow - player_5 157 120 255 // #9D78FF - Magenta - player_6 216 173 255 // #D8ADFF - Bright magenta - player_7 99 136 216 // #6388D8 - Blue - player_8 0 177 168 // #00B1A8 - Cyan - player_9 66 220 209 // #42DCD1 - Bright cyan - player_10 164 196 255 // #A4C4FF - Bright blue - } - } -} load_plugins { } - + // Use a simplified UI without special fonts (arrow glyphs) // Options: // - true @@ -414,7 +402,7 @@ load_plugins { // Choose the theme that is specified in the themes section. // Default: default - +// theme "australis" // Choose the base input mode of zellij. @@ -590,9 +578,16 @@ default_mode "normal" // Whether to show tips on startup // Default: true // -// show_startup_tips false +show_startup_tips false // Whether to show release notes on first version run // Default: true // // show_release_notes false + +// Web-client (zellij web pilot) — force VictorMono Nerd Font for ligatures. +// NOTE: resolves against the CLIENT device's installed fonts (no @font-face +// serving like the ttyd seat); each device needs the font installed. +web_client { + font "VictorMono Nerd Font" +} diff --git a/home_config/zellij/config.kdl.bak b/home_config/zellij/config.kdl.bak deleted file mode 100644 index fa30b12..0000000 --- a/home_config/zellij/config.kdl.bak +++ /dev/null @@ -1,452 +0,0 @@ -keybinds clear-defaults=true { - locked { - bind "Ctrl g" { SwitchToMode "normal"; } - } - pane { - bind "left" { MoveFocus "left"; } - bind "down" { MoveFocus "down"; } - bind "up" { MoveFocus "up"; } - bind "right" { MoveFocus "right"; } - bind "c" { SwitchToMode "renamepane"; PaneNameInput 0; } - bind "d" { NewPane "down"; SwitchToMode "normal"; } - bind "e" { TogglePaneEmbedOrFloating; SwitchToMode "normal"; } - bind "f" { ToggleFocusFullscreen; SwitchToMode "normal"; } - bind "h" { MoveFocus "left"; } - bind "i" { TogglePanePinned; SwitchToMode "normal"; } - bind "j" { MoveFocus "down"; } - bind "k" { MoveFocus "up"; } - bind "l" { MoveFocus "right"; } - bind "n" { NewPane; SwitchToMode "normal"; } - bind "p" { SwitchFocus; } - bind "Ctrl p" { SwitchToMode "normal"; } - bind "r" { NewPane "right"; SwitchToMode "normal"; } - bind "w" { ToggleFloatingPanes; SwitchToMode "normal"; } - bind "z" { TogglePaneFrames; SwitchToMode "normal"; } - } - tab { - bind "left" { GoToPreviousTab; } - bind "down" { GoToNextTab; } - bind "up" { GoToPreviousTab; } - bind "right" { GoToNextTab; } - bind "1" { GoToTab 1; SwitchToMode "normal"; } - bind "2" { GoToTab 2; SwitchToMode "normal"; } - bind "3" { GoToTab 3; SwitchToMode "normal"; } - bind "4" { GoToTab 4; SwitchToMode "normal"; } - bind "5" { GoToTab 5; SwitchToMode "normal"; } - bind "6" { GoToTab 6; SwitchToMode "normal"; } - bind "7" { GoToTab 7; SwitchToMode "normal"; } - bind "8" { GoToTab 8; SwitchToMode "normal"; } - bind "9" { GoToTab 9; SwitchToMode "normal"; } - bind "[" { BreakPaneLeft; SwitchToMode "normal"; } - bind "]" { BreakPaneRight; SwitchToMode "normal"; } - bind "b" { BreakPane; SwitchToMode "normal"; } - bind "h" { GoToPreviousTab; } - bind "j" { GoToNextTab; } - bind "k" { GoToPreviousTab; } - bind "l" { GoToNextTab; } - bind "n" { NewTab; SwitchToMode "normal"; } - bind "r" { SwitchToMode "renametab"; TabNameInput 0; } - bind "s" { ToggleActiveSyncTab; SwitchToMode "normal"; } - bind "Ctrl t" { SwitchToMode "normal"; } - bind "x" { CloseTab; SwitchToMode "normal"; } - bind "tab" { ToggleTab; } - } - resize { - bind "left" { Resize "Increase left"; } - bind "down" { Resize "Increase down"; } - bind "up" { Resize "Increase up"; } - bind "right" { Resize "Increase right"; } - bind "+" { Resize "Increase"; } - bind "-" { Resize "Decrease"; } - bind "=" { Resize "Increase"; } - bind "H" { Resize "Decrease left"; } - bind "J" { Resize "Decrease down"; } - bind "K" { Resize "Decrease up"; } - bind "L" { Resize "Decrease right"; } - bind "h" { Resize "Increase left"; } - bind "j" { Resize "Increase down"; } - bind "k" { Resize "Increase up"; } - bind "l" { Resize "Increase right"; } - bind "Ctrl n" { SwitchToMode "normal"; } - } - move { - bind "left" { MovePane "left"; } - bind "down" { MovePane "down"; } - bind "up" { MovePane "up"; } - bind "right" { MovePane "right"; } - bind "h" { MovePane "left"; } - bind "Ctrl h" { SwitchToMode "normal"; } - bind "j" { MovePane "down"; } - bind "k" { MovePane "up"; } - bind "l" { MovePane "right"; } - bind "n" { MovePane; } - bind "p" { MovePaneBackwards; } - bind "tab" { MovePane; } - } - scroll { - bind "e" { EditScrollback; SwitchToMode "normal"; } - bind "s" { SwitchToMode "entersearch"; SearchInput 0; } - } - search { - bind "c" { SearchToggleOption "CaseSensitivity"; } - bind "n" { Search "down"; } - bind "o" { SearchToggleOption "WholeWord"; } - bind "p" { Search "up"; } - bind "w" { SearchToggleOption "Wrap"; } - } - session { - bind "a" { - LaunchOrFocusPlugin "zellij:about" { - floating true - move_to_focused_tab true - } - SwitchToMode "normal" - } - bind "c" { - LaunchOrFocusPlugin "configuration" { - floating true - move_to_focused_tab true - } - SwitchToMode "normal" - } - bind "Ctrl o" { SwitchToMode "normal"; } - bind "p" { - LaunchOrFocusPlugin "plugin-manager" { - floating true - move_to_focused_tab true - } - SwitchToMode "normal" - } - bind "w" { - LaunchOrFocusPlugin "session-manager" { - floating true - move_to_focused_tab true - } - SwitchToMode "normal" - } - } - shared_except "locked" { - bind "Alt left" { MoveFocusOrTab "left"; } - bind "Alt down" { MoveFocus "down"; } - bind "Alt up" { MoveFocus "up"; } - bind "Alt right" { MoveFocusOrTab "right"; } - bind "Alt +" { Resize "Increase"; } - bind "Alt -" { Resize "Decrease"; } - bind "Alt =" { Resize "Increase"; } - bind "Alt [" { PreviousSwapLayout; } - bind "Alt ]" { NextSwapLayout; } - bind "Alt f" { ToggleFloatingPanes; } - bind "Ctrl g" { SwitchToMode "locked"; } - bind "Alt h" { MoveFocusOrTab "left"; } - bind "Alt i" { MoveTab "left"; } - bind "Alt j" { MoveFocus "down"; } - bind "Alt k" { MoveFocus "up"; } - bind "Alt l" { MoveFocusOrTab "right"; } - bind "Alt n" { NewPane; } - bind "Alt o" { MoveTab "right"; } - bind "Ctrl q" { Quit; } - } - shared_except "locked" "move" { - bind "Ctrl h" { SwitchToMode "move"; } - } - shared_except "locked" "session" { - bind "Ctrl o" { SwitchToMode "session"; } - } - shared_except "locked" "scroll" "search" "tmux" { - bind "Ctrl b" { SwitchToMode "tmux"; } - } - shared_except "locked" "scroll" "search" { - bind "Ctrl s" { SwitchToMode "scroll"; } - } - shared_except "locked" "tab" { - bind "Ctrl t" { SwitchToMode "tab"; } - } - shared_except "locked" "pane" { - bind "Ctrl p" { SwitchToMode "pane"; } - } - shared_except "locked" "resize" { - bind "Ctrl n" { SwitchToMode "resize"; } - } - shared_except "normal" "locked" "entersearch" { - bind "enter" { SwitchToMode "normal"; } - } - shared_except "normal" "locked" "entersearch" "renametab" "renamepane" { - bind "esc" { SwitchToMode "normal"; } - } - shared_among "pane" "tmux" { - bind "x" { CloseFocus; SwitchToMode "normal"; } - } - shared_among "scroll" "search" { - bind "PageDown" { PageScrollDown; } - bind "PageUp" { PageScrollUp; } - bind "left" { PageScrollUp; } - bind "down" { ScrollDown; } - bind "up" { ScrollUp; } - bind "right" { PageScrollDown; } - bind "Ctrl b" { PageScrollUp; } - bind "Ctrl c" { ScrollToBottom; SwitchToMode "normal"; } - bind "d" { HalfPageScrollDown; } - bind "Ctrl f" { PageScrollDown; } - bind "h" { PageScrollUp; } - bind "j" { ScrollDown; } - bind "k" { ScrollUp; } - bind "l" { PageScrollDown; } - bind "Ctrl s" { SwitchToMode "normal"; } - bind "u" { HalfPageScrollUp; } - } - entersearch { - bind "Ctrl c" { SwitchToMode "scroll"; } - bind "esc" { SwitchToMode "scroll"; } - bind "enter" { SwitchToMode "search"; } - } - renametab { - bind "esc" { UndoRenameTab; SwitchToMode "tab"; } - } - shared_among "renametab" "renamepane" { - bind "Ctrl c" { SwitchToMode "normal"; } - } - renamepane { - bind "esc" { UndoRenamePane; SwitchToMode "pane"; } - } - shared_among "session" "tmux" { - bind "d" { Detach; } - } - tmux { - bind "left" { MoveFocus "left"; SwitchToMode "normal"; } - bind "down" { MoveFocus "down"; SwitchToMode "normal"; } - bind "up" { MoveFocus "up"; SwitchToMode "normal"; } - bind "right" { MoveFocus "right"; SwitchToMode "normal"; } - bind "space" { NextSwapLayout; } - bind "\"" { NewPane "down"; SwitchToMode "normal"; } - bind "%" { NewPane "right"; SwitchToMode "normal"; } - bind "," { SwitchToMode "renametab"; } - bind "[" { SwitchToMode "scroll"; } - bind "Ctrl b" { Write 2; SwitchToMode "normal"; } - bind "c" { NewTab; SwitchToMode "normal"; } - bind "h" { MoveFocus "left"; SwitchToMode "normal"; } - bind "j" { MoveFocus "down"; SwitchToMode "normal"; } - bind "k" { MoveFocus "up"; SwitchToMode "normal"; } - bind "l" { MoveFocus "right"; SwitchToMode "normal"; } - bind "n" { GoToNextTab; SwitchToMode "normal"; } - bind "o" { FocusNextPane; } - bind "p" { GoToPreviousTab; SwitchToMode "normal"; } - bind "z" { ToggleFocusFullscreen; SwitchToMode "normal"; } - } -} - -// Plugin aliases - can be used to change the implementation of Zellij -// changing these requires a restart to take effect -plugins { - about location="zellij:about" - compact-bar location="zellij:compact-bar" - configuration location="zellij:configuration" - filepicker location="zellij:strider" { - cwd "/" - } - plugin-manager location="zellij:plugin-manager" - session-manager location="zellij:session-manager" - status-bar location="zellij:status-bar" - strider location="zellij:strider" - tab-bar location="zellij:tab-bar" - welcome-screen location="zellij:session-manager" { - welcome_screen true - } -} - -// Plugins to load in the background when a new session starts -// eg. "file:/path/to/my-plugin.wasm" -// eg. "https://example.com/my-plugin.wasm" -load_plugins { -} - -// Use a simplified UI without special fonts (arrow glyphs) -// Options: -// - true -// - false (Default) -// -// simplified_ui true - -// Choose the theme that is specified in the themes section. -// Default: default -// -// theme "dracula" - -// Choose the base input mode of zellij. -// Default: normal -// -// default_mode "locked" - -// Choose the path to the default shell that zellij will use for opening new panes -// Default: $SHELL -// -// default_shell "fish" - -// Choose the path to override cwd that zellij will use for opening new panes -// -// default_cwd "/tmp" - -// The name of the default layout to load on startup -// Default: "default" -// -// default_layout "compact" - -// The folder in which Zellij will look for layouts -// (Requires restart) -// -// layout_dir "/tmp" - -// The folder in which Zellij will look for themes -// (Requires restart) -// -// theme_dir "/tmp" - -// Toggle enabling the mouse mode. -// On certain configurations, or terminals this could -// potentially interfere with copying text. -// Options: -// - true (default) -// - false -// -// mouse_mode false - -// Toggle having pane frames around the panes -// Options: -// - true (default, enabled) -// - false -// -// pane_frames false - -// When attaching to an existing session with other users, -// should the session be mirrored (true) -// or should each user have their own cursor (false) -// (Requires restart) -// Default: false -// -// mirror_session true - -// Choose what to do when zellij receives SIGTERM, SIGINT, SIGQUIT or SIGHUP -// eg. when terminal window with an active zellij session is closed -// (Requires restart) -// Options: -// - detach (Default) -// - quit -// -// on_force_close "quit" - -// Configure the scroll back buffer size -// This is the number of lines zellij stores for each pane in the scroll back -// buffer. Excess number of lines are discarded in a FIFO fashion. -// (Requires restart) -// Valid values: positive integers -// Default value: 10000 -// -// scroll_buffer_size 10000 - -// Provide a command to execute when copying text. The text will be piped to -// the stdin of the program to perform the copy. This can be used with -// terminal emulators which do not support the OSC 52 ANSI control sequence -// that will be used by default if this option is not set. -// Examples: -// -// copy_command "xclip -selection clipboard" // x11 -// copy_command "wl-copy" // wayland -// copy_command "pbcopy" // osx -// -// copy_command "pbcopy" - -// Choose the destination for copied text -// Allows using the primary selection buffer (on x11/wayland) instead of the system clipboard. -// Does not apply when using copy_command. -// Options: -// - system (default) -// - primary -// -// copy_clipboard "primary" - -// Enable automatic copying (and clearing) of selection when releasing mouse -// Default: true -// -// copy_on_select true - -// Path to the default editor to use to edit pane scrollbuffer -// Default: $EDITOR or $VISUAL -// scrollback_editor "/usr/bin/vim" - -// A fixed name to always give the Zellij session. -// Consider also setting `attach_to_session true,` -// otherwise this will error if such a session exists. -// Default: -// -// session_name "My singleton session" - -// When `session_name` is provided, attaches to that session -// if it is already running or creates it otherwise. -// Default: false -// -// attach_to_session true - -// Toggle between having Zellij lay out panes according to a predefined set of layouts whenever possible -// Options: -// - true (default) -// - false -// -// auto_layout false - -// Whether sessions should be serialized to the cache folder (including their tabs/panes, cwds and running commands) so that they can later be resurrected -// Options: -// - true (default) -// - false -// -// session_serialization false - -// Whether pane viewports are serialized along with the session, default is false -// Options: -// - true -// - false (default) -// -// serialize_pane_viewport false - -// Scrollback lines to serialize along with the pane viewport when serializing sessions, 0 -// defaults to the scrollback size. If this number is higher than the scrollback size, it will -// also default to the scrollback size. This does nothing if `serialize_pane_viewport` is not true. -// -// scrollback_lines_to_serialize 10000 - -// Enable or disable the rendering of styled and colored underlines (undercurl). -// May need to be disabled for certain unsupported terminals -// (Requires restart) -// Default: true -// -// styled_underlines false - -// How often in seconds sessions are serialized -// -// serialization_interval 10000 - -// Enable or disable writing of session metadata to disk (if disabled, other sessions might not know -// metadata info on this session) -// (Requires restart) -// Default: false -// -// disable_session_metadata false - -// Enable or disable support for the enhanced Kitty Keyboard Protocol (the host terminal must also support it) -// (Requires restart) -// Default: true (if the host terminal supports it) -// -// support_kitty_keyboard_protocol false - -// Whether to stack panes when resizing beyond a certain size -// Default: true -// -// stacked_resize false - -// Whether to show tips on startup -// Default: true -// -// show_startup_tips false - -// Whether to show release notes on first version run -// Default: true -// -// show_release_notes false diff --git a/home_root/.claude/CLAUDE.md b/home_root/.claude/CLAUDE.md new file mode 100644 index 0000000..4e2563e --- /dev/null +++ b/home_root/.claude/CLAUDE.md @@ -0,0 +1,736 @@ +# CLAUDE.md — global preferences (lkraven) + +Loaded into every Claude Code session regardless of project. Project- +local `CLAUDE.md` files take precedence when they conflict; this file +is the baseline. + +## Operator identity + +The operator is **Vuong Hoang** (lkraven@lkraven.com), addressed as +"Vuong". Roles across three organizations: + +- **Corviduo AI** — Founder, Principal Developer, Chief Systems Architect. +- **PhaseFinal, Inc** — Founder, Principal Developer, Chief Systems Architect. +- **VastBlue AI** — Partner, Chief Systems Architect. + +He is the final authority on architectural / material-consequence +calls. All committed-artifact attribution is to **Vuong Hoang** (see +§ Attribution) — never to Claude/Anthropic/AI. + +## Core principles + +Four principles governing engineering choices across all projects. +Common lodestar: fitness-for-purpose. Each rejects a different +substitute goal that disguises itself as virtue. + +1. **Excellence over uniqueness.** Pick the shape that's right for + the problem, not the shape that's new. If uniqueness is a + byproduct of excellence, so be it; do not target it. + +2. **Explicit over implicit.** Make load-bearing assumptions, + constraints, and coupling visible. Idiomatic implicitness + (language conventions, well-known protocols) is fine — the rule + targets *invisible* implicitness, not all of it. Cost of + explicitness is verbosity; cost of invisible implicitness is + undetectable coupling. Pay the cost where auditability beats + the tax. + +3. **Elegance is a byproduct, not a target.** Excellent engineering + often produces elegant results; targeting elegance directly tends + to produce cleverness, which is a different thing. Review test: + "fit-for-purpose, debuggable, consistent" — not "feels nice." + +4. **Action-relevance over thoroughness.** Before investigating, + ask whether the answer would change the action. If both outcomes + lead to the same default, skip the question. + +## Operator cognitive-load reduction (core mission) + +A core mission: **reduce the operator's cognitive load as he context- +shifts across dozens of projects.** He knows the detailed workings of +each — the job is not to teach, it is to let him re-enter any project +and act without reloading its full context from scratch. Three +standing obligations layered on top of normal work: + +1. **Surface every architecture decision that matters to him.** + Material-consequence decisions — module boundaries, naming with + downstream reach, scope direction, anything hard to reverse — go to + him, not a peer, not a silent default. (The operator-owns- + architectural-calls rule, restated as a load-reduction duty.) Do + **not** dumb the decision down — he wants the real substance. + +2. **Pair every operator-facing decision with an `/elitk` decision + tree.** When you surface a decision for him to make, accompany the + technical framing with a King-grade (`/elitk`) decision tree: + plain-language, jargon-free, the branches and where each leads, so + he can decide in seconds without reloading the project's context. + The tree is the load-reducer; the technical framing is the + substance — give both, every time. Lead with your recommendation. + +3. **Keep a running `/elitk` summary of decisions made on his behalf.** + Every call you take autonomously (agent-discretion: patch bumps, + tie-breaks, routing, sequencing, implementation-level choices) earns + a plain-language line in a decisions summary, so he can audit what + happened in his absence without digging through the work. Offer the + `/elitk` summary unprompted at natural checkpoints and whenever he + asks "what have you decided?" + +4. **Always attach the real technical detail — never make him ask for + it.** Every `/elitk` decision tree and every decisions-summary line + carries its actual technical specifics alongside the plain-language, + clearly delineated (parentheses, brackets, an indented technical + note, a `details:` tail — your pick) so it can be read or skipped at + a glance. Plain-language is the fast-scan default; the technical + version rides *with* it, never behind a follow-up request. **He + chooses his own cognitive load by deciding whether to read the + bracketed detail** — the job is to make both present, not to + pre-decide which he needs. Example: "kept the version cadence + conservative `[F03 research work → patch v0.5.4 then v0.5.5; the two + docs-only edits → no bump per the SemVer docs-only skip-rule]`." + +This extends the non-technical-breakdown habit (auto-memory +`feedback_non_technical_breakdown_on_findings`) from findings to *all* +decisions, and sharpens it with the decision-tree-for-pending-calls +requirement. `/elitk` is the grade and the tool; its King framing — +smart reader, zero jargon — is exactly the operator mid-context-shift. + +## Cross-frontier-model consultation discipline + +Consults to non-Claude peer models (Heid = Codex/gpt-5.x oracle; +Eitri-Smithy = Codex-shaped embedded research peer; any future +non-Claude reviewer agent) operate under **two complementary +anti-failure framings**: + +1. **Authority-frame**: peers provide **fresh perspective**, not + **superior reasoning**. Different model families have different + priors and different training cutoffs — they catch different + blind spots. That is the load-bearing value, not authority. +2. **Role-frame**: peers are **peer-reviewers, not operator-level + oracles**. Routing operator-level decisions (architectural + choices, scope direction, what-to-build calls with material + consequence) to a peer for an authoritative answer is misuse — + those go to the operator regardless of what the peer says. + Peers can pressure-test, paraphrase, surface ambiguity, and + recommend implementation-level calls; they don't decide. + +Together: peers are useful reviewers whose value comes from a +different angle, not better answers; the operator owns the +architectural calls, period. + +**Default skepticism, never auto-adopt.** Every cross-frontier reply +gets triaged before any of it lands in a plan, contract, or commit. +Five-category triage: + +1. **Genuine add** — peer caught something my framing genuinely + missed. Take it. +2. **Sharpening** — peer reframed something I already had into a + sharper version. Merge into the existing variable; don't + double-count as net-new. +3. **Restatement of settled prior** — peer flagged as "missed" a + thing that is already settled in our findings/contracts/ADRs. + Useful as a coverage checklist; **not** a structural addition. +4. **Out-of-place** — peer's input is correct but belongs at a + different stage (e.g., implementation-stage detail surfaced + during frame-stage). Note and defer. +5. **Wrong-grounding** — peer reasoned from incomplete context and + manufactured a non-issue. Push back; do not adopt. + +**Watch for the ignorance-of-context failure mode.** A peer's "you +missed X" claim has two possible sources: (a) genuine fresh insight, +or (b) the peer did not read the artifact where X is settled. Before +treating an omission claim as new, check what the peer actually +read — if your settled artifact wasn't on their reading list, the +"miss" is probably category 3 or 5, not category 1. + +**Adoption criterion**: "this improves the outcome on its merits" +— never "the peer said so." Treating Codex (or any non-Claude +oracle) as an all-knowing authority is a category error; treat it +as a wise sounding board whose value is forcing you to defend +your framing against a differently-primed reader. + +**Applies hardest to `/heid*` skills.** Those skills' artifact-only +discipline protects the EMITTING side (Heid can't be primed by the +caller); the CONSUMING side rubber-stamp risk is on whoever invokes +the skill. Always triage Heid output before acting on it. + +## Althing async-reply monitoring + +When you ship an althing message that you expect to come back with a +reply — a consult (`/heid*`), a question to a peer agent, a request +awaiting an answer — and you do **not** already have an althing +monitor running for this session, **always start one** +(`/althing:monitor`). The dispatch and the monitor are one action: you +opened a loop, so arm the thing that closes it. + +Why: in an interactive session the inbox banner (📬) only fires on the +*next* user prompt. If the operator steps away after you dispatch an +async consult, the reply can sit unseen indefinitely — the loop you +opened never closes on its own. A monitor actively pings when the +reply lands, so the await-reply promise ("reply will land in your +inbox") is backed by a real notification, not by hoping the operator +prompts again. + +Scope: only messages where you're waiting on a response. Pure +fire-and-forget posts (an FYI ping, a post-1.0 downstream-consumer +announcement) don't need a monitor. When the exchange is done, sign +off (`/althing:sign-off`) so monitors don't accumulate — stop a stuck +one with `althing-light-monitor --stop`, never `pkill` (that kills +every agent's monitor, not just yours). + +## Development workflow shapes + +Two end-to-end shapes for taking a unit of work from idea to landed. +Pick by whether the work will be implemented by an AFK agent +(Sleipnir dispatch) or directly in-session. Both front-load the same +deliberation — heid consult → contract → heid contract review → +contract fixup — so the spec is sound before any code; they diverge +only at the implementation handoff. + +Stages are defaults, not a rigid gate. Skip a stage when it would be +ceremony (a trivial bug fix needs no vor pass; a one-liner needs no +contract). The heid consult and review stages follow the cross- +frontier triage discipline above: never auto-adopt. + +**Contract-skip is a direct-implementation privilege.** In the direct +shape, low-effort work — surgical test updates, localized bug fixes, +one-liners — runs straight to implementation without a fresh contract, +*provided* any existing contract governing the touched behavior is +updated in the same commit so it stays canonical (skip *authoring* a +contract, never let a live one go stale against the code). This does +not extend to AFK dispatch, which always requires a `prd:`-pinned +contract per § "Issue → AFK dispatch hygiene (Sleipnir)" (hard policy) +— and trivial work is not an AFK candidate in the first place: the +scaffold → contract → review → preflight ritual carries real overhead +that only pays for itself on higher-effort tasks. When in doubt, +low-effort stays direct. + +**Graphify the blast radius BEFORE writing the contract** — for ANY +contract that touches existing code (not greenfield). Run a graphify +pass over every symbol the contract will change, delete, or rename +(`graphify explain ` for callers/neighbors, `graphify path` +for reach) so the contract's scope section enumerates the *true* set +of affected files and call-sites — not an under-count that detonates +mid-implementation. **Cross-check with grep, always.** Graphify is +blind to function-local and DI-injected imports (`from .x import y` +inside a function body, store objects passed through constructors) — +the exact pattern that hides cross-module callers. Graphify confirms +the call structure; grep catches what its AST extraction misses; +neither alone is sufficient (graphify-only under-counts, grep-only +misses transitive reach). Both, before the contract — the scope +section is only as honest as the blast-radius pass behind it. (Lesson: +a contract that names 3 files when the rip touches 6 is a scope miss +the up-front pass would have caught.) + +### AFK dispatch shape (Sleipnir) + +For work an AFK agent will implement off a contract. + +1. Issue created. +2. Heid consult on the issue (`/heid`) — pressure-test the framing. +3. Scaffold the issue for AFK dispatch (`/sleipnir-scaffold `) — + writes the `prd:`-pinned contract frontmatter. +4. Contract generation — the architect writes the contract body. +5. Heid contract review (`/heid-contract-review`). +6. Fixup the contract per triaged findings. +7. Preflight + mark ready-for-agent (`/sleipnir-preflight `) — the + irreversible dispatch authorization. See § "Issue → AFK dispatch + hygiene (Sleipnir)" for the gate's hard requirements. + +### Direct implementation shape + +For work implemented directly in-session. + +1. Issue created. +2. Heid consult on the issue (`/heid`). +3. `/vor` — or `/vor-frame` (ask too shapeless to draft a `/vor` + questionnaire) or `/vor-cross` (cross-frontier peer in the loop) — + if the ask warrants a pre-contract design pass. Skip when the + frame is already crisp. +4. Write the contract. +5. Heid contract review (`/heid-contract-review`). +6. Fixup the contract per triaged findings. +7. TDD implement (`/tdd`) — red-green-refactor against the contract. +8. Heid code review (`/heid-code-review`) — code-vs-contract drift. +9. Fixup the code per triaged findings. +10. Commit if clean. + +## Tooling preferences + +- **Python: prefer `uv` whenever possible.** Use `uv venv`, `uv pip + install`, `uv run`, `uv tool install`, etc. instead of `python -m + venv`, `pip`, `pipx`, `poetry`. Exception: if the project clearly + uses something else (a `poetry.lock` exists, the README says + `pip-tools`, the Dockerfile already pins `pip install`), follow the + project's tooling — don't fight it just to use uv. +- **Shell: I run interactive `zsh`.** Commands you hand me to paste + must be zsh-safe: quote glob-bearing args (`'pkg[extra]'`, not + `pkg[extra]` — zsh errors `no matches found` on unquoted brackets) + and don't rely on bash-only syntax. Inline `#` comments are fine + (`setopt interactive_comments` is set in `~/.zshrc`). Claude Code's + Bash tool also runs zsh here (it follows `$SHELL`), so the same + applies to tool commands. + +## Global tools available + +Tools standing ready in the working environment — assume present and use +them without a setup detour. (Most Claude Code sessions run natively on +**nh3-dev** `10.100.10.50`, the dev box; "available" means there unless a +note says otherwise. On other boxes, check first.) + +- **Playwright + headless Chromium** — installed **box-wide on nh3-dev**: + the system shared-libs (apt, via `playwright install-deps`), the browser + binaries in shared **`/opt/ms-playwright`** (chromium + headless-shell, + root-owned + world-readable), and + `PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright` wired globally + (`/etc/environment` + `/etc/profile.d/`). A project only needs the + `playwright` module (`npm i playwright` — the browser download is + skippable, it resolves the shared binary); **no per-project + `playwright install`**. Use for anything that needs a real browser + engine: true DOM/SVG layout geometry, screenshots, headless rendering, + browser-based tests or scraping. Add a browser / bump via + `ssh infra-ops@10.100.10.50 'sudo env PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright npx -y playwright install '`. + +- **Graphify** — open-source knowledge-graph skill for AI coding assistants + (`uv tool install graphifyy`; CLI `graphify`, MCP `graphify-mcp`). Turns a + repo into a queryable graph. The **free, deterministic path** is + `graphify update `: tree-sitter AST extraction + Leiden clustering, + **zero LLM / zero tokens**, ~25s for ~900 files; writes + `graphify-out/{graph.json,GRAPH_REPORT.md}`. Highest-value free output is + the **God Nodes** list (core-abstraction surfacing) plus `graphify explain + ` / `query` / `affected` / `path` traversals over `EXTRACTED` call + edges. The LLM layer (community **labeling** = low-caliber, and **semantic + extraction** of docs/INFERRED edges = higher-caliber + noisier) is optional + and points at any backend via `~/.graphify/providers.json` — pair labeling + with the cheap LiteLLM/Granite endpoint below; leave deep semantic + extraction off unless needed (its INFERRED/AMBIGUOUS edges fight the + explicit-over-implicit floor). Complements a hand-authored `docs/CODEBASE.md` + (curated geography), doesn't replace it. Pilot-validated on Worldtree + 2026-06-10. + +- **LiteLLM gateway (Granite 4.1 + friends)** — OpenAI-compatible gateway at + `http://10.250.50.70:4000/v1` (Logs UI `:4000/ui`) fronting vLLM services. + Essentially-**free local compute** for low-caliber, high-volume, + parallelizable LLM work. The **always-available summarizer / classifier + endpoint** is `granite-4.1-8b` — the go-to for summarization, naming/ + labeling, classification, and triage (fast, parallelizes well on vLLM): + served FP8 on ana-ml2 GPU 1, production-stable, 131k context (rebalanced + 2026-06-13). Treat it as a standing dependency you can reach for any time. + Also behind the gateway: `qwen3.5-9b-fp8` (vision / multimodal — image + + text chat, GPU 1), `qwen3-embedding`, `qwen3-reranker`, and `glm-5.1` / + `glm-4.7` (via z.ai passthrough). The gateway 401s without a **virtual API + key**. **Shared all-agents key** — internal-only gateway, scoped to the *free + local* models only (granite + qwen-vision + embed/rerank, NOT the paid GLM): + `sk-eA_XOdcs6nIkyYXonohtEQ` (alias `all-agents-local`). Use it directly for + the always-available local endpoints — no per-project provisioning needed. + Example: `curl http://10.250.50.70:4000/v1/chat/completions -H "Authorization: + Bearer sk-eA_XOdcs6nIkyYXonohtEQ" -d '{"model":"granite-4.1-8b","messages":[…]}'`. + For the **paid** GLM passthroughs or broader scope, still request a + project-scoped key from infra-ops via althing (the shared key deliberately + can't spend z.ai cost). Rotatable via infra-ops if it leaks. Reach for + this before spending premium API tokens on tasks a small fast local model + handles fine (the LLM-last-resort floor still applies — deterministic first). + +## DevOps delegation + +For all DevOps work — deployments, infrastructure provisioning, +server administration, credential management, system-level package +installs, network/firewall configuration, service uptime — **prefer +routing to `infra-ops` via althing** rather than handling directly +in a project-dev session. + +When infra-ops needs auth keys or access it doesn't already hold, +**it asks the appropriate peer agent directly** rather than blocking +on the operator. Examples: + +- Heimdall scopes (Worldtree auth) → ask `worldtree-dev`. +- Gitea API tokens / repo permissions → ask the peer that holds + admin access for the relevant org. +- Vendor / subscription API keys (OpenAI, Anthropic, etc.) → ask + the operator or the peer that owns the consuming service. +- Cloud / hosting credentials → ask the operator. + +The principle: infra-ops is self-sufficient on the access-gathering +front. Exhaust peer-agent paths before escalating to the operator. +The operator (Vuong) is the final authority for credentials not in +any agent's possession, but the agent network should resolve +credential routing among itself when possible. + +## Attribution + +All attributions are to **Vuong Hoang**. Never reference Claude, +Claude Code, Anthropic, "AI-assisted", "Generated with …", or robot +emojis anywhere — git commit messages, PR bodies, README author lines, +LICENSE copyright, `pyproject.toml` authors, file headers, HTML +footers, or any other byline. The user is the author; the assistant +is invisible. + +Specifically, never append: + +- `Co-Authored-By: Claude …` +- `🤖 Generated with [Claude Code](…)` +- "with help from Claude / an AI assistant / etc." + +…to any committed artifact. + +## SemVer etiquette + +**Canonical policy reference:** the formal versioning policy for ALL +projects lives at +`~/development/corviduo-project-template/docs/VERSIONING-POLICY.md` +(canonical, self-pinned as `versioning-policy-v1`). It governs version +format (SemVer 2.0.0), the public-contract declaration each project +must make, pre-1.0 semantics, pre-release staging (`-alpha.N` / +`-beta.N` / `-rc.N`, the same-commit rc discipline), dev/in-progress +labeling per ecosystem, commit conventions, the release process, and +special cases (hotfix, deprecation, breaking security fix, build +metadata). Consult it for any versioning question this section doesn't +answer — especially pre-releases and release staging, which this +section doesn't cover. + +This section is Corviduo's **operational cadence layer on top of that +policy**: which commits become releases and at what tier (tier-bar +test, patch-default, operator approval for non-patch). Where the two +differ on cadence or tier choice, this section governs; for format, +contract semantics, pre-release staging, and release mechanics, the +policy governs. Operator ruling (2026-07-02): pre-1.0 **milestone +minors stay legal** — this section's non-breaking milestone-minor +examples override the policy's § 4 strict reading (pre-1.0 minor = +breaking changes only). Settled; do not re-litigate. + +Each code-bearing or substantive-artifact commit bumps a version in +the project's `pyproject.toml` (or equivalent), refreshes the +lockfile, gets a `vX.Y.Z` git tag. Establishes a fine-grained +changelog and lets any peer pin against a known-good substrate +state. + +**Tier-bar test (the decision tool):** + +> *Does this release warrant a release note — or, in the case of a +> post-1.0 release, does it rise to the level of requiring a push +> notification to subscribed downstream consumers? Does this release +> warrant a blog post or a press release?* + +The escalation maps to communication metaphors: + +| Tier | Question | Communication metaphor | +|---|---|---| +| **Patch** (default) | n/a — patch is the workhorse default | Changelog line | +| **Minor** (pre-1.0) | Does this warrant a **release note**? | Subscribers glance at it | +| **Minor** (post-1.0) | Does this rise to **push-notification to subscribed downstream consumers**? | We actively reach out | +| **Major** | Does this warrant a **blog post or a press release**? | Story worth telling beyond subscribers | + +The crucial implicit calibration: **the bar for minor RISES at v1.0.** +Pre-1.0, minor is cheap (would you write a release-note paragraph?); +post-1.0, minor is expensive (would you actually interrupt downstream +consumers about this?). Pre-1.0 is iterative cheap-minors; post-1.0 +is stable deliberate-minors. Crossing v1.0 is a discipline-tightening +event, not just a number. + +**In Corviduo,** "push notification to subscribed downstream +consumers" maps concretely to: **an althing post to peer-dev handles +announcing the change**. That's the actual mechanism. Operator or +Brokkr pings worldtree-dev / galdrabok-dev / sleipnir-dev / etc. +when a post-1.0 minor lands. + +### Operator approval required for any non-patch bump + +**Patch bumps are at agent discretion.** Apply them autonomously as +part of the work. + +**Minor and major bumps REQUIRE explicit operator approval** before +the bump commit lands. The mechanism: + +1. Identify that the work warrants a non-patch bump per the + tier-bar test (release-note / push-notification / blog-post). +2. Surface to the operator with a concrete question: + *"This work appears minor-worthy because . Approve the + vX.Y.Z → vX.Y+1.0 bump?"* +3. **Wait for explicit approval** before applying the bump. +4. Apply the bump (edit pyproject.toml, run `uv lock`, commit, tag) + only after the operator has said yes. + +This guard exists because non-patch bumps in agent-applied autonomy +drifted upward — the operator caught a minor-cadence-too-fast +pathology on 2026-05-25 (three bumps in ~60 min, two of them +minor; would land at v0.4823.2 territory at sustained pace). The +tier-bar test alone wasn't sufficient discipline; explicit +operator-in-the-loop is the corrective. + +If the operator pre-authorizes a session-level batch (e.g., "just +ship the next minor when you reach the heid-orchestrator milestone"), +that constitutes approval — but the pre-authorization should be +explicit and bounded to a specific upcoming event, not "all minors +going forward." + +This rule applies UNIVERSALLY — even in repos where push is +agent-authorized (like Brokkr-Smithy per its project-local +CLAUDE.md). Push-discretion and bump-discretion are independent +authorizations. + +### Patch (`X.Y.Z` → `X.Y.Z+1`) — workhorse default + +The default for any code-bearing or substantive-artifact commit. +Includes: + +- Bug fixes +- Internal correctness improvements +- Refactors with no public-surface signature change +- New artifacts within an existing family (new R-target finding, new + spec doc, new feedback memory) +- Single-commit features that don't require downstream coordination +- Validator additions, security tightenings, drift-against-spec + corrections +- Skill specs authored (spec drafted; minor fires at implementation + ship) + +Patches accumulate routinely. Most commits are patches. + +### Minor (`X.Y.Z` → `X.Y+1.0`) — coordinated-release event + +Reserved for events where the tier-bar test fires: would you write a +release note (pre-1.0) or send a push notification to downstream +consumers (post-1.0)? + +Pre-1.0, the bar is "release-note-worthy paragraph." Post-1.0, it's +the stricter "actually-pinging-consumers." Examples that pass at +either bar: + +- **Cross-repo deliverable shipped to a peer agent** (e.g., Brokkr + R10 A01 ContextPromotion intelligence package → worldtree-dev). + Downstream HAS to react. +- **R-target full closure** — multi-finding + action lifecycle + complete; the package is the milestone landmark. +- **Skill IMPLEMENTATION shipped** at the implementation site + (post-spec absorption). Callers must adapt. +- **Methodology charter changes** (e.g., adding noise-floor + cross-check ambient to Brokkr's preregistration discipline). + High-leverage; affects every probe going forward. +- **Pre-v1.x breaking change** — under the no-backward-compatibility + rule, breaking changes ride in minor until v1.0. Breaking changes + are intrinsically release-note-worthy; minor is the right tier. +- **API signature change to publicly-callable surface** post-1.0 + that affects fewer callers than a full major (e.g., new optional + parameter, opt-in capability). Still warrants the + push-notification. + +Examples that DON'T pass the tier-bar and stay as patches: + +- A single new R-target finding (F-ID) within an active R-target's + lifecycle. The closure is the milestone, not each finding. +- A new feedback memory written. +- A spec doc authored (minor fires at implementation, not at spec). +- A new probe artifact added within an existing probe. +- A persistent-memory snapshot. + +### Major (`X.Y.Z` → `X+1.0.0`) — blog-post / press-release event + +Reserved for v1.0+ release milestones AND post-1.0 events whose +scale warrants telling people beyond your subscriber base. + +Pre-v1.x, the no-backward-compat rule means every minor can carry +breakage; major is the milestone-cut signal (v1.0 itself), not a +per-PR concern. + +Post-v1.0: + +- v1.0 release itself (the discipline-tightening crossover event). +- Substantial breaking changes that would otherwise force most + consumers to adapt at once. +- Architectural restructurings that downstream documentation, + integrations, and conceptual-mental-models depend on. + +### Decision rule when ambiguous + +**Default to patch.** Minor and major require explicit justification. +If the answer to "does this warrant a release note?" is "maybe, kind +of, I guess?" — that's a patch. Reserve minor for the unambiguous +yes-I'd-write-a-paragraph cases. + +**When both minor and patch can be justified, default to patch.** +Tie-breaks go to patch, not minor. The "both could apply" situation +is itself a signal that minor's mandate isn't strong — if minor were +clearly warranted, patch wouldn't also be defensible. Patch is the +workhorse default; let it work. This is a sharper statement of the +same posture above: the bar for non-patch is "patch is NOT defensible +for this commit," not "minor is defensible." Asymmetric default in +favor of the cheaper tier. + +The old "two callers react differently" rule remains a useful +internal check: if breaking-vs-keep-working applies to current +callers, it's at least minor. But the tier-bar is the load-bearing +test. + +### SKIP the bump entirely for + +- CI-only edits +- True docs-only edits (READMEs, comments, formatting) +- ADR commits with no code +- `.contract.md` commits with no code +- Memory-snapshot commits (`memory: snapshot — …`) +- Test-only commits with no production-code change +- WIP / TDD-RED commits where the tree is in a known-broken state +- Brokkr-side template edits with no runtime effect on Brokkr itself +- Worktree-rebuild or env-config changes +- `.claude/` configuration edits + +### Cadence + +Most commits should be patches. Minor should be rarer than commits — +often 5-15 patches between minors during active development. + +A TDD red/green/refactor cycle bumps once or twice (GREEN as patch, +REFACTOR as patch — never three times for the same logical change). +A feature shipped as several incremental green commits bumps each as +patch; the minor that "publishes" the feature can fire at the end of +the arc if it warrants a release-note, OR can be skipped if patches +were sufficient. + +If you find yourself bumping minor multiple times per session on the +same project, you're likely over-applying minor. Default back to +patch and ask: does this *specific* commit warrant a release-note +paragraph, or am I just accumulating substantive work? + +### Mechanics every bump + +0. **If non-patch, get operator approval first** per the § *Operator + approval required for any non-patch bump* rule above. Do not + proceed to step 1 until the operator has explicitly approved the + minor or major bump. (Patch bumps skip this step — apply + autonomously.) +1. Edit `pyproject.toml` (or equivalent project-file) version field. +2. Run `uv lock` (Python) or equivalent so the lockfile records the + new version. Don't hand-edit the lockfile. +3. Stage both files alongside the actual change. +4. Commit with the conventional shape (e.g., `feat(#N): …` / + `fix(#N): …` / `refactor: …`). +5. `git tag vX.Y.Z` after the commit. Lightweight (no `-a`, no + message) is deliberate for per-bump tags: the commit object + carries the rationale, and the tag is just a ref naming the + substrate state. + **Exception — milestone releases** (v1.0, v2.0, future major + cuts) get annotated tags (`git tag -a -m "…"`, or `git tag -s` + when signing). At a milestone the attestation matters separately + from the code change: release notes, tagger identity + date, and + optional GPG signature live in the tag object itself, where + downstream pins and supply-chain verifiers can read them. +6. Push is the operator's call — never push automatically. + (Project-local `CLAUDE.md` may grant push discretion, e.g., + Brokkr-Smithy; honor those when present.) + +### Calibration examples + +**Brokkr-Smithy 2026-05-25 session (the recalibration trigger):** + +Initially bumped under the pre-amendment rule: + +- `v0.1.0 → v0.2.0` (minor) — F02 R06 v2 taxonomy reduction ship +- `v0.2.0 → v0.3.0` (minor) — Heid orchestrator spec set v0.1 +- `v0.3.0 → v0.3.1` (patch) — migration runbook revision + +Under THIS amended rule: + +- F02 ship would be **patch** — single finding within R06 v2's + active lifecycle. R06 v2 closure (multi-finding + action) gets + the minor. +- Heid orchestrator spec set — **patch** strictly (spec authored, + not yet implemented). Galdrabok-dev's skill spec implementation + shipping IS minor (callers adapt). +- Runbook revision — **patch** (correct under both rules). + +The session would have ended at roughly v0.1.5 (five patches) with +maybe a v0.2.0 cut when the heid orchestrator's smoke-tests-pass + +galdrabok ship lands as the cohesive milestone. + +**Worldtree #185 (2026-05-21):** + +- `v0.20.0` (minor) — `ChromaLongTermMemory.store/search/retrieve/ + forget` gained required keyword-only `end_user_id`. Pre-v1.x + breaking change; every caller had to be updated. Release-note- + worthy in pre-1.0 framing. **Minor** under both old and new + rules. +- `v0.20.1` (patch) — Heid-flagged drift fixes within the v0.20.0 + surface. **Patch** under both rules. + +### Retroactive bumps + +Don't retroactively bump past commits to align with a tightened +rule. Substrate-state baseline is forward-looking: the bump-cadence +from this point forward reflects the discipline; past versions +record what happened under the previous rule. The pre/post boundary +is the amendment-effective commit, not the rule itself. + +## Roadmap discipline — v1 target + parking lot (anti-creep) + +Projects sprawl at v0 when features land with **no v1 target to gate +them against** — every good idea, lacking a home, becomes v0 scope by +default, and the project never converges. This is the feature-axis +analog of two disciplines already in force: **value-of-information** +for probes (don't-measure-what-won't-change-behavior) and +**patch-default** for version bumps. Same posture — +*asymmetric default toward not-doing* — applied to features. + +**Every project carries a `ROADMAP.md`** (a screenful, not a PRD): + +1. **v1 target** — the small, explicit "what must be true to cut + 1.0": **3–7 capabilities**. A big v1 is itself the creep; keep it + tight. This is the done-definition AND the anchor every feature is + gated against. + +2. **Parking lot (post-v1 / vNext / spinout)** — every deferred idea, + named, with a home. Creep lands *here* instead of silently + becoming v0 scope. A "feature" that's really its own project is a + **spinout**, not v1 scope. + +3. **The gate (the anti-creep mechanism):** a proposed feature → *on + the v1 path? → in; else → parking lot.* **Default = parking lot.** + When both "v1" and "park" are defensible, park it — the same + tie-break-to-the-cheaper-tier asymmetry as the SemVer patch-default. + Applies regardless of who proposed the feature (operator included); + the gate is about v1-path-fit, not provenance. + +4. **Creep-check at checkpoints:** when surfacing progress, ask "is + what we're building on the v1 path?" Off-path-but-worth-keeping → + parking lot, surfaced to the operator, never silently into v0. + +The per-project `ROADMAP.md` file shape + location is defined in +`corviduo-project-template` as a copied-in **skeleton** (like +`persistent-memory.md` — new projects copy it, then the content +diverges per project), NOT a byte-identical canonical. The skeleton +carries the shape; the discipline itself lives in this rule (loaded +every session), so no byte-sync of content is needed. This mirrors +the SemVer global-rule + per-project-version-file split: the version +*field* is conventional, its *value* diverges per project — nobody +canonical-syncs version values, and likewise nobody syncs roadmap +content. v1.0 is cut when the ROADMAP's v1 target is met — tying +directly into the SemVer "v1.0 = discipline-tightening event." + +## Issue → AFK dispatch hygiene (Sleipnir) + +Before marking any issue `ready-for-agent` (the label that gates AFK +dispatch via Sleipnir), it MUST have an issue-numbered contract at +`docs/contracts/issues/.contract.md` AND that contract MUST carry +a `prd:` block in its YAML frontmatter pinning it to: + +- the issue body (SHA-256, first 16 hex chars) +- the lock-in comment id + content SHA (or `null` if the issue body + alone is the spec) +- a `pinned_at` timestamp + +This is hard policy, not a suggestion. Without the contract Sleipnir's +gate refuses dispatch with `blocked-needs-contract`. Without the `prd:` +block PRD-↔-ship drift becomes invisible — the audit infrastructure +that detects it is a no-op. + +Verify before applying the label: + +- contract file exists at the expected path +- frontmatter has a populated `prd:` block +- `python scripts/contract_drift_check.py --contract ` returns + clean (i.e., the pinned hashes match the live issue + comment) + +The full convention — frontmatter shape, required fields, reasoning +behind the four drift entry points — lives in +`docs/contracts/CONTRACT-FORMAT.md` (the project that consumes +Sleipnir owns this file). Module-scoped contracts MAY adopt the same +`prd:` block when amended in response to a specific issue; for +issue-scoped contracts it is required. diff --git a/home_root/.p10k.zsh b/home_root/.p10k.zsh index df1c9db..f98ef80 100644 --- a/home_root/.p10k.zsh +++ b/home_root/.p10k.zsh @@ -105,12 +105,18 @@ # Blue current directory. typeset -g POWERLEVEL9K_DIR_FOREGROUND=$lightergrey - # Context format when root: user@host. The first part white, the rest grey. + # Context sits just left of the clock and shows the hostname, with the + # session type made obvious at a glance: + # native (local) -> grey hostname + # ssh (remote) -> yellow hostname tagged " ssh" + # root -> user@host, username in white typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f" - # Context format when not root: user@host. The whole thing grey. typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f" - # Don't show context unless root or in SSH. - typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION= + typeset -g POWERLEVEL9K_CONTEXT_DEFAULT_TEMPLATE="%F{$grey}%m%f" + typeset -g POWERLEVEL9K_CONTEXT_REMOTE_TEMPLATE="%F{$yellow}%m%f%F{$grey} ssh%f" + typeset -g POWERLEVEL9K_CONTEXT_REMOTE_SUDO_TEMPLATE="%F{$white}%n%f%F{$yellow}@%m%f%F{$grey} ssh%f" + # Keep only the local-sudo state quiet (matches Pure); every other state shows. + typeset -g POWERLEVEL9K_CONTEXT_SUDO_CONTENT_EXPANSION= # Show previous command duration only if it's >= 5s. typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5 @@ -136,22 +142,21 @@ typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan # Don't show remote branch, current tag or stashes. typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind) - # Don't show the branch icon. - typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= - # When in detached HEAD state, show @commit where branch normally goes. - typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@' - # Don't show staged, unstaged, untracked indicators. - typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON= - # Show '*' when there are staged, unstaged or untracked files. - typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*' - # Show '⇣' if local branch is behind remote. - typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣' - # Show '⇡' if local branch is ahead of remote. - typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡' - # Don't show the number of commits next to the ahead/behind arrows. - typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1 - # Remove space between '⇣' and '⇡' and all trailing spaces. - typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }' + # Nerd Font branch glyph () before the branch name. + typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=$' ' + # Detached HEAD: git-commit glyph () where the branch name normally goes. + typeset -g POWERLEVEL9K_VCS_COMMIT_ICON=$' ' + # Distinct Nerd Font glyphs, each followed by its count, for the working-tree state: + # staged · unstaged (modified, not staged) · untracked. + # A category only renders when its count is non-zero. + typeset -g POWERLEVEL9K_VCS_STAGED_ICON=$' ' + typeset -g POWERLEVEL9K_VCS_UNSTAGED_ICON=$' ' + typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON=$' ' + # No separate catch-all dirty marker — the three glyphs above are the breakdown. + typeset -g POWERLEVEL9K_VCS_DIRTY_ICON= + # Nerd Font behind/ahead arrows (/), each followed by the commit count. + typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=$' ' + typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=$' ' # Grey current time. typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey diff --git a/home_root/.stow-global-ignore b/home_root/.stow-global-ignore index 067cc2e..f1592b8 100644 --- a/home_root/.stow-global-ignore +++ b/home_root/.stow-global-ignore @@ -1,5 +1,4 @@ ~/.config/tmux/plugins -~/.config/thefuck/__pycache__ ~/.config/btop ~/.config/neofetch ~/.config/zinit diff --git a/home_root/.wezterm.lua b/home_root/.wezterm.lua index 7206e34..d680ee4 100644 --- a/home_root/.wezterm.lua +++ b/home_root/.wezterm.lua @@ -110,7 +110,7 @@ config.colors = { config.window_decorations = "RESIZE" config.default_prog = { 'pwsh' } else - config.window_background_opacity = 0.85 + config.window_background_opacity = 0.95 config.macos_window_background_blur = 40 config.enable_tab_bar = false config.window_decorations = "RESIZE" diff --git a/home_root/.zshrc b/home_root/.zshrc index 90388d1..ac30fe2 100644 --- a/home_root/.zshrc +++ b/home_root/.zshrc @@ -3,9 +3,12 @@ path=(~/bin $path) path=(~/.local/bin $path) path=(~/.cache/lk-tools/fzf/bin $path) - -# Init OhMyPosh -eval "$(oh-my-posh init zsh --config https://333e8677ac54a1845fe68813017ab7e807c87ab2@gitea.phasefinal.com/vh/dotfiles/raw/branch/master/home_config/ohmyposh/lkraven.toml)" +# Enable Powerlevel10k instant prompt. Should stay close to the top of ~/.zshrc. +# Initialization code that may require console input (password prompts, [y/n] +# confirmations, etc.) must go above this block; everything else may go below. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi # Set ZINIT Home ZINIT_HOME="${HOME}/.config/zinit/zinit.git" @@ -20,6 +23,10 @@ fi source "${ZINIT_HOME}/zinit.zsh" # add zinit +# Powerlevel10k prompt theme (replaces oh-my-posh; instant prompt = fast startup) +zinit ice depth=1 +zinit light romkatv/powerlevel10k + zinit light zsh-users/zsh-syntax-highlighting zinit light zsh-users/zsh-completions zinit light zsh-users/zsh-autosuggestions @@ -53,6 +60,10 @@ setopt hist_ignore_dups setopt hist_find_no_dups setopt hist_verify +# allow inline `#` comments at the interactive prompt (paste-friendly; +# matches non-interactive/script behavior so pasted commands don't fail) +setopt interactive_comments + # completion styling zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}" @@ -62,12 +73,10 @@ zstyle ':fzf-tab:complete:__zoxide_z:*' fzf-preview 'ls --color $realpath' zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup # aliases -alias ls='eza' alias cat='bat' -alias tree='eza --tree' # To customize prompt, run `p10k configure` or edit ~/.p10k.zsh. -# [[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh +[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh [ -f ~/.fzf.zsh ] && source ~/.fzf.zsh export FZF_DEFAULT_COMMAND="fd --hidden --strip-cwd-prefix --exclude .git" @@ -83,13 +92,13 @@ _fzf_compgen_dir() { source ~/fzf-git.sh/fzf-git.sh export FZF_CTRL_T_OPTS="--preview 'bat -n --color=always --line-range :500 {}'" -export FZF_ALT_C_OPTS="--preview 'eza --tree --color=always {} | head -200'" +export FZF_ALT_C_OPTS="--preview 'ls -la {} | head -200'" _fzf_comprun() { local command=$1 shift case "$command" in - cd) fzf --preview 'eza --tree --color=always {} | head -200' "$@" ;; + cd) fzf --preview 'ls -la {} | head -200' "$@" ;; export|unset) fzf --preview "eval 'echo \$' {}" "$@" ;; ssh) fzf --preview 'dig {}' "$@" ;; *) fzf --preview 'bat -n --color=always --line-range :500 {}' "$@" ;; @@ -99,19 +108,28 @@ export BAT_THEME=Nord eval "$(zoxide init zsh)" alias cd='z' -eval $(thefuck --alias) -eval $(thefuck --alias fk) - zinit light Aloxaf/fzf-tab -if [[ $(uname) == "Darwin" ]]; then - source $(brew --prefix)/share/zsh-autosuggestions/zsh-autosuggestions.zsh -elif [[ $(uname) == "Linux" ]]; then - source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh +# zsh-autosuggestions is already loaded via zinit above; on Linux, also wire +# the terminfo arrow keys to history search. +if [[ $(uname) == "Linux" ]]; then bindkey "${terminfo[kcuu1]}" history-search-backward bindkey "${terminfo[kcud1]}" history-search-forward fi -export STM32_PRG_PATH=/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin \ No newline at end of file +export STM32_PRG_PATH=/Applications/STMicroelectronics/STM32Cube/STM32CubeProgrammer/STM32CubeProgrammer.app/Contents/MacOs/bin + +# bun completions +[ -s "/home/lkraven/.bun/_bun" ] && source "/home/lkraven/.bun/_bun" + +# bun +export BUN_INSTALL="$HOME/.bun" +export PATH="$BUN_INSTALL/bin:$PATH" + +# >>> grok installer >>> +export PATH="$HOME/.grok/bin:$PATH" +fpath=(~/.grok/completions/zsh $fpath) +autoload -Uz compinit && compinit -C +# <<< grok installer <<< diff --git a/link-dotfiles b/link-dotfiles index dd39023..576b59f 100755 --- a/link-dotfiles +++ b/link-dotfiles @@ -1,3 +1,61 @@ -#!/bin/bash +#!/usr/bin/env bash +# One-shot, host-aware dotfile linker for Linux & macOS. +# +# home_root/* -> ~/ (e.g. ~/.zshrc) via `stow home_root` +# home_config/* -> ~/.config/ (e.g. ~/.config/nvim) via `stow home_config` +# +# Ensures GNU stow is present first (Homebrew on macOS; apt/dnf/pacman on Linux), +# so a fresh box needs nothing pre-installed. Re-running is safe — stow is +# idempotent and refuses to clobber unrelated real files. +# +# Windows / PowerShell: run ./link-dotfiles.ps1 instead (stow has no native +# Windows port; the .ps1 mirrors this with junctions + symlinks). +set -euo pipefail + +repo="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$repo" + +# `--apps`: install all host CLI tools first, then link. Without it, this only +# links (fast + idempotent). App install is delegated to the host-aware +# lk-installers/install-apps. +if [ "${1:-}" = "--apps" ]; then + echo "Installing apps first (--apps)…" + "$repo/lk-installers/install-apps" +fi + +ensure_stow() { + command -v stow >/dev/null 2>&1 && return + + echo "GNU stow not found — installing it…" + case "$(uname -s)" in + Darwin) + if ! command -v brew >/dev/null 2>&1; then + echo "error: Homebrew is required on macOS. Run ./lk-installers/install-apps (or ./link-dotfiles --apps) first." >&2 + exit 1 + fi + brew install stow + ;; + Linux) + if command -v apt >/dev/null 2>&1; then sudo apt update && sudo apt install -y stow + elif command -v dnf >/dev/null 2>&1; then sudo dnf install -y stow + elif command -v pacman >/dev/null 2>&1; then sudo pacman -S --noconfirm stow + else + echo "error: no supported package manager (apt/dnf/pacman). Install GNU stow manually." >&2 + exit 1 + fi + ;; + *) + echo "error: unsupported OS '$(uname -s)'. On Windows run ./link-dotfiles.ps1 instead." >&2 + exit 1 + ;; + esac +} + +ensure_stow + +echo "Linking home_root -> ~" stow -v home_root -stow -v --target=$HOME/.config/ home_config +echo "Linking home_config -> ~/.config" +stow -v --target="$HOME/.config" home_config + +echo "Done." diff --git a/link-dotfiles.ps1 b/link-dotfiles.ps1 new file mode 100644 index 0000000..06b8fcc --- /dev/null +++ b/link-dotfiles.ps1 @@ -0,0 +1,116 @@ +#!/usr/bin/env pwsh +<# +.SYNOPSIS + Windows equivalent of ./link-dotfiles (the GNU stow script). + +.DESCRIPTION + stow can't run natively on Windows, so this mirrors what it does: + home_root/* -> ~\ (e.g. ~\.zshrc) + home_config/* -> ~\.config\ (e.g. ~\.config\nvim) + + Directories are linked with junctions and files with symlinks/hardlinks, + none of which need administrator rights (junctions and hardlinks never do; + symlinks are attempted first in case Developer Mode is on, then we fall + back). Re-running is safe: correct links are skipped, and any real file or + directory that would be clobbered is moved aside to .bak first. + +.EXAMPLE + pwsh -File .\link-dotfiles.ps1 +#> +[CmdletBinding()] +param( + # Print what would happen without changing anything. + [switch]$DryRun +) + +$ErrorActionPreference = 'Stop' +$repo = $PSScriptRoot +$home_ = $HOME + +# stow metadata files we must never link into the home tree. +$skip = @('.stow-local-ignore', '.stow-global-ignore') + +function Get-LinkTarget { + param([string]$Path) + $item = Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue + if ($item -and $item.LinkType) { return $item.Target } + return $null +} + +function Link-Item { + param( + [string]$Source, # absolute path inside the repo + [string]$Dest # absolute path under the home tree + ) + + $isDir = (Get-Item -LiteralPath $Source -Force).PSIsContainer + + # Already linked to the right place? Nothing to do. + $existingTarget = Get-LinkTarget $Dest + if ($existingTarget -and ($existingTarget -ieq $Source)) { + Write-Host " ok $Dest" -ForegroundColor DarkGray + return + } + + if (Test-Path -LiteralPath $Dest) { + if ($existingTarget) { + # A link, but pointing elsewhere -> replace it. + if ($DryRun) { Write-Host " relink $Dest (was -> $existingTarget)" -ForegroundColor Yellow; return } + Remove-Item -LiteralPath $Dest -Force -Recurse + } else { + # A real file/dir -> back it up rather than destroy it. + $bak = "$Dest.bak" + Write-Host " backup $Dest -> $bak" -ForegroundColor Yellow + if (-not $DryRun) { Move-Item -LiteralPath $Dest -Destination $bak -Force } + } + } + + if ($DryRun) { Write-Host " link $Dest -> $Source" -ForegroundColor Cyan; return } + + $parent = Split-Path $Dest -Parent + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } + + if ($isDir) { + New-Item -ItemType Junction -Path $Dest -Target $Source | Out-Null + Write-Host " junc $Dest -> $Source" -ForegroundColor Green + } else { + # Prefer a real symlink (needs admin or Developer Mode); fall back to a + # hardlink (same volume, no admin); finally just copy. + try { + New-Item -ItemType SymbolicLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null + Write-Host " link $Dest -> $Source" -ForegroundColor Green + } catch { + try { + New-Item -ItemType HardLink -Path $Dest -Target $Source -ErrorAction Stop | Out-Null + Write-Host " hard $Dest -> $Source" -ForegroundColor Green + } catch { + Copy-Item -LiteralPath $Source -Destination $Dest -Force + Write-Host " copy $Dest (could not link; copied)" -ForegroundColor Magenta + } + } + } +} + +Write-Host "home_root -> $home_" +Get-ChildItem -LiteralPath (Join-Path $repo 'home_root') -Force | + Where-Object { $skip -notcontains $_.Name } | + ForEach-Object { Link-Item $_.FullName (Join-Path $home_ $_.Name) } + +$configDir = Join-Path $home_ '.config' +Write-Host "home_config -> $configDir" +Get-ChildItem -LiteralPath (Join-Path $repo 'home_config') -Force | + Where-Object { $skip -notcontains $_.Name } | + ForEach-Object { Link-Item $_.FullName (Join-Path $configDir $_.Name) } + +# Windows Terminal lives in a package folder outside the stow tree, so link its +# settings.json explicitly. Skipped silently if Terminal isn't installed. +$wtPkg = Get-ChildItem (Join-Path $env:LOCALAPPDATA 'Packages') -Filter 'Microsoft.WindowsTerminal_*' -Directory -ErrorAction SilentlyContinue | + Select-Object -First 1 +$wtSrc = Join-Path $repo 'windows\windows-terminal\settings.json' +if ($wtPkg -and (Test-Path $wtSrc)) { + $wtDest = Join-Path $wtPkg.FullName 'LocalState\settings.json' + Write-Host "windows-terminal -> $wtDest" + Link-Item $wtSrc $wtDest +} + +Write-Host "Done." -ForegroundColor Green diff --git a/lk-installers/install-apps b/lk-installers/install-apps new file mode 100755 index 0000000..a04c084 --- /dev/null +++ b/lk-installers/install-apps @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# Host-aware installer for every CLI tool the dotfiles assume at runtime. +# +# macOS -> Homebrew (bootstraps Homebrew itself if missing) +# Linux -> apt (Debian/Ubuntu) + upstream installers for tools not in apt +# +# zinit and all zsh plugins (powerlevel10k, zsh-autosuggestions, +# zsh-syntax-highlighting, zsh-completions, zsh-vi-mode, fzf-tab) bootstrap +# themselves from ~/.zshrc on first shell start — they are NOT installed here. +# +# Idempotent: re-running only fills gaps. Pairs with ./link-dotfiles, or run +# `./link-dotfiles --apps` to install then link in one shot. +# +# Consolidates the former macinstaller / install-apt-pkgs / install-tools / +# install-zellij scripts. +set -euo pipefail + +# fzf-git.sh is a sourced helper (not a package) — same on every platform. +clone_fzf_git() { + if [ -d "$HOME/fzf-git.sh" ]; then + echo "fzf-git.sh already cloned — skipping" + else + git clone --depth 1 https://github.com/junegunn/fzf-git.sh.git "$HOME/fzf-git.sh" + fi +} + +install_macos() { + if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew not found — installing…" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + if [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)" + elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)" + fi + fi + + echo "Installing CLI tools via Homebrew…" + brew install \ + zsh git curl wget stow \ + bat fd fzf zoxide \ + zellij neovim btop htop fastfetch + # tldr is deprecated upstream — best-effort so a dead formula can't abort the + # run. tlrc is the maintained `tldr` client. + brew install tlrc || brew install tldr || echo "tldr unavailable — skipping" + + # fzf key-bindings + completion -> ~/.fzf.zsh (sourced by .zshrc). These flags + # make it non-interactive; --no-update-rc stops it editing .zshrc (the repo + # owns that file). + "$(brew --prefix)/opt/fzf/install" --key-bindings --completion --no-update-rc >/dev/null + + clone_fzf_git +} + +install_linux() { + if ! command -v apt >/dev/null 2>&1; then + echo "error: Linux app install targets Debian/Ubuntu (apt). Install the tools manually on this distro." >&2 + exit 1 + fi + + echo "Installing base packages via apt…" + sudo apt update + sudo apt install -y --no-install-recommends \ + neovim vim-nox btop htop curl wget tldr git zip unzip stow zsh cargo \ + zsh-autosuggestions zsh-syntax-highlighting + sudo apt install -y --no-install-recommends rar unrar || true + # fastfetch (modern neofetch); falls back to neofetch on older distros. + sudo apt install -y fastfetch || sudo apt install -y neofetch || echo "fastfetch/neofetch unavailable — skipping" + + # fzf (vendored under ~/.cache/lk-tools/fzf; .zshrc adds its bin to PATH) + if [ ! -d "$HOME/.cache/lk-tools/fzf" ]; then + git clone --depth 1 https://github.com/junegunn/fzf.git "$HOME/.cache/lk-tools/fzf" + "$HOME/.cache/lk-tools/fzf/install" --key-bindings --completion --no-update-rc + fi + clone_fzf_git + + # zoxide (upstream installer -> ~/.local/bin, already on PATH) + command -v zoxide >/dev/null 2>&1 || \ + curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh + + # bat + fd: upstream .deb so the binaries are named `bat`/`fd` (Debian ships + # them as `batcat`/`fdfind`). amd64 assumed — adjust the URLs on arm64 boxes. + if ! command -v bat >/dev/null 2>&1; then + wget -qO /tmp/bat.deb https://github.com/sharkdp/bat/releases/download/v0.24.0/bat_0.24.0_amd64.deb + sudo apt install -y /tmp/bat.deb + fi + if ! command -v fd >/dev/null 2>&1; then + wget -qO /tmp/fd.deb https://github.com/sharkdp/fd/releases/download/v10.1.0/fd_10.1.0_amd64.deb + sudo apt install -y /tmp/fd.deb + fi + + # zellij: cargo build, fall back to apt + command -v zellij >/dev/null 2>&1 || cargo install zellij || sudo apt install -y zellij +} + +case "$(uname -s)" in + Darwin) install_macos ;; + Linux) install_linux ;; + *) echo "error: unsupported OS '$(uname -s)'." >&2; exit 1 ;; +esac + +echo "Done. Open a new shell (or 'exec zsh') to pick everything up." diff --git a/lk-installers/install-apt-pkgs b/lk-installers/install-apt-pkgs deleted file mode 100755 index 7f4e9be..0000000 --- a/lk-installers/install-apt-pkgs +++ /dev/null @@ -1,3 +0,0 @@ -sudo apt update -sudo apt install neovim vim-nox btop htop neofetch curl wget tldr git zip unzip stow zsh zsh-autosuggestions zsh-syntax-highlighting cargo --no-install-recommends -sudo apt install rar unrar --no-install-recommends diff --git a/lk-installers/install-eza b/lk-installers/install-eza deleted file mode 100755 index b09cb78..0000000 --- a/lk-installers/install-eza +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Check if Eza is installed - -if dpkg-query -Wf'${db:Status-abbrev}' eza; then - sudo apt install -y eza -else - sudo apt update - sudo apt install gpg - sudo mkdir -p /etc/apt/keyrings - wget -qO- https://raw.githubusercontent.com/eza-community/eza/main/deb.asc | sudo gpg --dearmor -o /etc/apt/keyrings/gierens.gpg - echo "deb [signed-by=/etc/apt/keyrings/gierens.gpg] http://deb.gierens.de stable main" | sudo tee /etc/apt/sources.list.d/gierens.list - sudo chmod 644 /etc/apt/keyrings/gierens.gpg /etc/apt/sources.list.d/gierens.list - sudo apt update - sudo apt install -y eza -fi - diff --git a/lk-installers/install-ohmyposh b/lk-installers/install-ohmyposh deleted file mode 100755 index fc99f28..0000000 --- a/lk-installers/install-ohmyposh +++ /dev/null @@ -1 +0,0 @@ -curl -s https://ohmyposh.dev/install.sh | bash -s diff --git a/lk-installers/install-thefk b/lk-installers/install-thefk deleted file mode 100755 index c66aa2f..0000000 --- a/lk-installers/install-thefk +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -sudo apt update -sudo apt install python3-dev python3-pip python3-setuptools python3-venv -python3 -m venv ~/homebase -source ~/homebase/bin/activate -pip3 install thefuck -mv ~/homebase/bin/fuck ~/.local/bin/ -mv ~/homebase/bin/thefuck ~/.local/bin/ diff --git a/lk-installers/install-tools b/lk-installers/install-tools deleted file mode 100755 index ac2cec2..0000000 --- a/lk-installers/install-tools +++ /dev/null @@ -1,8 +0,0 @@ -git clone --depth 1 https://github.com/junegunn/fzf.git ~/.cache/lk-tools/fzf -~/.cache/lk-tools/fzf/install -git clone https://github.com/junegunn/fzf-git.sh.git ~/fzf-git.sh -curl -sS https://raw.githubusercontent.com/ajeetdsouza/zoxide/main/install.sh | sh -wget -O /tmp/bat.deb https://github.com/sharkdp/bat/releases/download/v0.24.0/bat_0.24.0_amd64.deb -sudo apt install /tmp/bat.deb -wget -O /tmp/fdfind.deb https://github.com/sharkdp/fd/releases/download/v10.1.0/fd_10.1.0_amd64.deb -sudo apt install /tmp/fdfind.deb diff --git a/lk-installers/install-zellij b/lk-installers/install-zellij deleted file mode 100755 index 1047423..0000000 --- a/lk-installers/install-zellij +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -echo "Installing Zellij via cargo..." -cargo install zellij || { - echo "Cargo failed - trying apt instead" - sudo apt update && sudo apt install -y zellij -} diff --git a/lk-installers/macinstaller b/lk-installers/macinstaller deleted file mode 100755 index f57777b..0000000 --- a/lk-installers/macinstaller +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# filepath: /Users/vhpfi/dotfiles/lk-installers/macinstaller - -# Check if Homebrew is installed -if command -v brew >/dev/null 2>&1; then - echo "Homebrew is already installed. Updating..." - brew update -else - echo "Homebrew not found. Installing Homebrew..." - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - - # Add Homebrew to PATH for the current session - if [[ -f /opt/homebrew/bin/brew ]]; then - # For Apple Silicon Macs - eval "$(/opt/homebrew/bin/brew shellenv)" - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile - echo "Homebrew has been installed and added to your PATH." - elif [[ -f /usr/local/bin/brew ]]; then - # For Intel Macs - eval "$(/usr/local/bin/brew shellenv)" - echo 'eval "$(/usr/local/bin/brew shellenv)"' >> ~/.zprofile - echo "Homebrew has been installed and added to your PATH." - else - echo "Error: Couldn't find the Homebrew binary after installation." - exit 1 - fi -fi - -echo "Installing packages with Homebrew..." -brew install neovim btop htop neofetch curl wget git zip unzip zsh tldr -brew install zsh-autosuggestions zsh-syntax-highlighting -brew install rustup-init -rustup-init -y - -echo "Installation complete!" \ No newline at end of file diff --git a/windows/windows-terminal/settings.json b/windows/windows-terminal/settings.json new file mode 100644 index 0000000..66d46c1 --- /dev/null +++ b/windows/windows-terminal/settings.json @@ -0,0 +1,143 @@ +{ + "$help": "https://aka.ms/terminal-documentation", + "$schema": "https://aka.ms/terminal-profiles-schema", + "actions": + [ + { + "command": + { + "action": "copy", + "singleLine": false + }, + "id": "User.copy.644BA8F2" + }, + { + "command": "paste", + "id": "User.paste" + }, + { + "command": + { + "action": "splitPane", + "split": "auto", + "splitMode": "duplicate" + }, + "id": "User.splitPane.A6751878" + }, + { + "command": "find", + "id": "User.find" + } + ], + "copyFormatting": "none", + "copyOnSelect": false, + "defaultProfile": "{574e775e-4f2a-5b96-ac1e-a2962a402336}", + "keybindings": + [ + { + "id": "User.copy.644BA8F2", + "keys": "ctrl+c" + }, + { + "id": "User.find", + "keys": "ctrl+shift+f" + }, + { + "id": "User.paste", + "keys": "ctrl+v" + }, + { + "id": "User.splitPane.A6751878", + "keys": "alt+shift+d" + } + ], + "newTabMenu": + [ + { + "type": "remainingProfiles" + } + ], + "profiles": + { + "defaults": + { + "colorScheme": "Australis", + "font": + { + "face": "VictorMono Nerd Font", + "size": 11, + "features": + { + "calt": 1, + "liga": 1 + } + }, + "antialiasingMode": "grayscale", + "cursorShape": "bar", + "intenseTextStyle": "all", + "opacity": 95, + "padding": "10", + "scrollbarState": "visible", + "snapOnInput": true, + "useAcrylic": false + }, + "list": + [ + { + "commandline": "%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe", + "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", + "hidden": false, + "name": "Windows PowerShell" + }, + { + "commandline": "%SystemRoot%\\System32\\cmd.exe", + "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", + "hidden": false, + "name": "Command Prompt" + }, + { + "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}", + "hidden": false, + "name": "Azure Cloud Shell", + "source": "Windows.Terminal.Azure" + }, + { + "elevate": true, + "guid": "{574e775e-4f2a-5b96-ac1e-a2962a402336}", + "hidden": false, + "name": "PowerShell", + "opacity": 95, + "source": "Windows.Terminal.PowershellCore", + "useAcrylic": true + } + ] + }, + "schemes": + [ + { + "name": "Australis", + "foreground": "#a9bcc3", + "background": "#222531", + "cursorColor": "#6388d8", + "selectionBackground": "#6388d8", + "black": "#222531", + "red": "#ff491a", + "green": "#16b866", + "yellow": "#e1c631", + "blue": "#6388d8", + "purple": "#9d78ff", + "cyan": "#00b1a8", + "white": "#a9bcc3", + "brightBlack": "#373b46", + "brightRed": "#ff854f", + "brightGreen": "#51e08a", + "brightYellow": "#ffe14e", + "brightBlue": "#a4c4ff", + "brightPurple": "#d8adff", + "brightCyan": "#42dcd1", + "brightWhite": "#cce7ec" + } + ], + "themes": [], + "windowingBehavior": "useAnyExisting" +} \ No newline at end of file