Files
dotfiles-public/private_dot_config/nvim/lua/plugins/minuet.lua
T
bcherb2 b487b0e855 feat: public dotfiles tier — no credential, no identity, one installer
Fresh history. This is the repo a throwaway VM clones anonymously: it brings a
machine to a working baseline and carries nothing that makes it mine.

56 files. 50 land in $HOME, 3 are chezmoi metadata, 2 are repo documentation,
1 is the manifest, and a 15-file test harness stays behind in .tests/.

What did not travel, and why:

  encrypted_private_bws-token.age   a real credential; age is dropped entirely
  .chezmoidata/bws.toml             env-var -> secret-id map; belongs with the
                                    tier that can use it
  SECRETS.md                        documentation of the rules, not config
  finish-setup.sh.tmpl              superseded by dotup
  nvim/init.lua.backup              dead file
  dot_claude/**, dot_codex/**,      120 files of agent config, private tier
  dot_pi/**

De-identified rather than dropped:

  .gitconfig   [user], the GitHub ssh rewrite and both Gitea host rewrites are
               identity, not configuration. They move behind an [include] of
               ~/.config/git/config.local, which the private tier writes. Git
               treats a missing include as a no-op, so a public-only machine
               reads the file and stops.
  .zshrc       the two gitea aliases carried a personal domain and a LAN IP.
               They move behind a guarded source of ~/.config/zsh/local.zsh,
               the sibling of the secrets.zsh seam phase 2 established.
  nvim         a commented-out LM Studio endpoint naming a LAN address.
  ghostty      a stale auto-generated header naming an absolute home directory.

Newly captured, never tracked before: ~/.zshenv, ~/.config/gh/config.yml. The
former sourced ~/.cargo/env unguarded, so every zsh on a machine without rustup
printed an error -- the same shape as the unguarded oh-my-zsh source phase 2
fixed. It is guarded now.

.chezmoiexternal.toml grows from one entry to six. oh-my-zsh, powerlevel10k,
zsh-autosuggestions, zsh-ai and tpm were hand-installed and declared nowhere,
which is why `chezmoi init --apply` on a clean box produced a .zshrc that broke
the shell it configures. The theme and both plugins nest under
.oh-my-zsh/custom/, which is what $ZSH_CUSTOM resolves to.

dotup gains an install engine. It resolves each selected package to a channel
(apt, brew, npm, uv, snap, deb, flatpak, tarball, script, builtin) through one
function every consumer reads, probes apt-cache before batching so a name apt
does not know moves to brew instead of failing all thirty, and retries
individually if a batch still fails -- which earned its keep on the first real
container run, where mermaid-cli's puppeteer dependency failed and the other
twelve npm packages installed anyway. --unattended computes safe defaults fresh
from the manifest rather than inheriting a state file, and refuses private and
invasive rows outright even when a stale state file ticks them.

The manifest gains @spec, a second directive kind alongside @needs, carrying the
argument a channel needs but a package name cannot supply -- the scoped npm
name, the flatpak app id, the .deb source. The TSV stays five columns wide.

Three bugs the container runs found, all fixed here:

  * `apt install nodejs` gives you node WITHOUT npm on Ubuntu, so all thirteen
    npm packages failed on a fresh box. The manifest asks apt for both names.
  * A tool installed a moment ago is not on this process's PATH -- uv lands in
    ~/.local/bin, npm -g honours the ~/.npmrc prefix, linuxbrew is outside a
    non-login PATH. Resolved by looking in the places we just wrote to, never by
    exporting a modified PATH.
  * `A || { B && C; }` is one || list, so when `command -v sudo` failed the list
    failed and `set -e` killed dotup at load. On a non-root machine with no
    sudo it died before printing anything. There is a regression test.

.zshenv and .p10k.zsh are marked private_. Both are shell code the login shell
executes and both applied at 664, group-writable. Third occurrence of the class
of bug phase 1 found on .pi/agent/auth.json and phase 2 found on .zshrc; the
first one found on purpose rather than by accident.

Verification: 81 assertions, 81/81 on this box and in ubuntu:24.04, ubuntu:22.04
and debian:12. The installer is driven against a directory of fake package
managers that record what they were asked to do and install nothing, so the
engine is exercised end to end without a package landing on the test machine.
`gitleaks detect` over the full history and the working tree: no leaks found,
with no allowlist and no .gitleaks.toml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 00:24:41 -04:00

116 lines
5.0 KiB
Lua

-- Inline LLM (ghost-text) completion tuned for writing markdown/prose.
-- Reuses ZAI_API_KEY from the environment (same key the :AI command uses).
-- Suggestions are virtual text, auto-triggered behind a debounce delay.
local PROSE_FT = {
markdown = true,
["md-render"] = true,
text = true,
rst = true,
tex = true,
gitcommit = true,
mail = true,
}
return {
"milanglacier/minuet-ai.nvim",
dependencies = { "nvim-lua/plenary.nvim" },
-- Load eagerly: minuet only sets its buffer-local auto-trigger flag from a
-- FileType autocmd registered in setup(). Lazy-loading on InsertEnter would
-- register that autocmd *after* the current buffer's FileType already fired,
-- so virtualtext would never auto-trigger on the buffer you're editing.
lazy = false,
opts = {
provider = "openai_compatible",
-- One natural continuation, not three alternatives — prose has a single
-- "next sentence", and fewer candidates means less to read past.
n_completions = 1,
-- Give the model more of the text *before* the cursor (default 0.75).
context_ratio = 0.85,
-- Cap how much surrounding text is sent. Smaller = lower, steadier latency
-- (the full default of 16000 chars is overkill for prose continuation and
-- inflates time-to-first-token). 4000 chars ≈ plenty of preceding context.
context_window = 4000,
-- Delay knobs: wait for an idle pause, and rate-limit requests. Groq is fast
-- and deterministic, so we can afford a snappier debounce than the cloud
-- defaults; throttle stays moderate to respect the free-tier rate limit.
throttle = 800, -- min ms between requests
debounce = 400, -- ms of idle typing before a request fires
provider_options = {
openai_compatible = {
api_key = "GROQ_API_KEY", -- env var NAME; minuet reads it at runtime
name = "Groq",
-- Groq's LPU inference gives ~80-100ms time-to-first-token with very low
-- variance — that consistency is the whole reason for moving off Z.AI's
-- hit-or-miss coding endpoint. OpenAI-compatible, so the schema is unchanged.
end_point = "https://api.groq.com/openai/v1/chat/completions",
-- llama-3.3-70b is Groq's flagship general model: strong prose, still fast
-- on their hardware. If you ever want the absolute lowest latency, swap to
-- "llama-3.1-8b-instant" (faster, slightly weaker prose).
model = "llama-3.3-70b-versatile",
-- Swap minuet's "code completion engine" system prompt for a prose one
-- in writing filetypes; keep the default (string) for code. Guidelines
-- and few-shots are left at defaults so minuet's <endCompletion> parsing
-- still works.
system = {
prompt = function()
if PROSE_FT[vim.bo.filetype] then
return [[
You are a prose writing assistant embedded in a text editor. Continue the
author's text at the <cursorPosition> marker naturally and fluently, matching
their voice, tone, and sentence rhythm.
- Output ONLY the continuation text — no preamble, no explanation.
- Never wrap the output in markdown code fences or backticks.
- Do not add headings, bullets, or numbering unless the surrounding text
already uses them.
- Keep it to at most 1-3 sentences and stop at a natural boundary.]]
end
return require("minuet.config").default_system_prefix_first.prompt
end,
},
optional = {
-- Short, focused completions. (No `thinking` field here — that was a
-- Z.AI-specific param; Groq's Llama models 400 on unknown body fields.)
max_tokens = 96,
temperature = 0.3,
},
},
},
virtualtext = {
auto_trigger_ft = { "*" },
-- Alt keymaps are kept as a secondary path, but the primary accept key is
-- <Tab>, wired in config() below (Alt+* doesn't fire in Ghostty without
-- macos-option-as-alt, and Tab is the natural "accept" key for prose).
keymap = {
accept = "<A-y>", -- accept full suggestion
accept_line = "<A-l>", -- accept one line
prev = "<A-[>",
next = "<A-]>",
dismiss = "<A-e>",
},
},
},
config = function(_, opts)
require("minuet").setup(opts)
-- Smart <Tab>: accept a visible minuet ghost-text suggestion; otherwise fall
-- back to the exact super-tab behavior we had before (jump an active snippet,
-- else insert a literal Tab). Blink's menu navigates with <C-n>/<C-p>, not
-- Tab, so this doesn't fight the completion popup.
local vt = require("minuet.virtualtext").action
vim.keymap.set("i", "<Tab>", function()
if vt.is_visible() then
vt.accept()
elseif vim.snippet.active({ direction = 1 }) then
vim.snippet.jump(1)
else
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes("<Tab>", true, false, true),
"n",
false
)
end
end, { desc = "minuet: accept suggestion, else snippet jump / Tab" })
end,
}