Compare commits
11 Commits
fe449d5682
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b2aae384e0 | |||
| 6905cdf3be | |||
| 3c6afb9d3a | |||
| 2192fa23f0 | |||
| e232d1ec2e | |||
| 15f8e17b57 | |||
| 41c5a6c37d | |||
| 1510881558 | |||
| 13a252517a | |||
| b2033a7b2d | |||
| adbac68c9e |
@@ -1,3 +0,0 @@
|
||||
.DS_Store
|
||||
*-walkthrough.html
|
||||
!templates/reference-walkthrough.html
|
||||
@@ -1,88 +0,0 @@
|
||||
# PR Walkthrough
|
||||
|
||||
Retrieved from https://github.com/houshuang/pr-walkthrough
|
||||
|
||||
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill that generates beautiful, self-contained HTML walkthroughs of pull requests.
|
||||
|
||||
Instead of reading diffs line by line, get a narrative explanation of *what* changed, *why* it was designed that way, and *how* the pieces connect — presented as a polished technical article.
|
||||
|
||||
<!-- TODO: Add screenshot -->
|
||||

|
||||
|
||||
## What It Generates
|
||||
|
||||
A single self-contained HTML file with:
|
||||
|
||||
- **Narrative structure** — Sections build understanding incrementally, from big picture to implementation details
|
||||
- **Real code excerpts** — Actual code from the PR with syntax highlighting, not pseudocode
|
||||
- **Architecture diagrams** — Flow diagrams, layer stacks, and sequence diagrams showing how components connect
|
||||
- **Annotated tradeoffs** — Why this approach was chosen over alternatives
|
||||
- **Extension recipes** — How to add a similar feature following the same patterns
|
||||
|
||||
The design aesthetic is editorial — think Stripe engineering blog post, not GitHub diff view.
|
||||
|
||||
## Design System
|
||||
|
||||
The walkthrough uses a warm paper theme with an editorial typography stack:
|
||||
|
||||
- **Instrument Serif** — Display headings
|
||||
- **Source Serif 4** — Body text
|
||||
- **DM Mono** — Code, labels, metadata
|
||||
|
||||
Components include flow diagrams, layer stacks, callout boxes (insight/warning/pattern/tradeoff), comparison tables, file trees, and syntax-highlighted code blocks. See `templates/reference-walkthrough.html` for the full component library.
|
||||
|
||||
## Install
|
||||
|
||||
Copy the skill into your Claude Code skills directory:
|
||||
|
||||
```bash
|
||||
# Clone the repo
|
||||
git clone https://github.com/houshuang/pr-walkthrough.git
|
||||
|
||||
# Copy to your Claude Code skills directory
|
||||
cp -r pr-walkthrough ~/.claude/skills/pr-walkthrough
|
||||
```
|
||||
|
||||
Or if you prefer to keep it as a symlink:
|
||||
|
||||
```bash
|
||||
ln -s /path/to/pr-walkthrough ~/.claude/skills/pr-walkthrough
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
From any git repository with a PR or feature branch checked out:
|
||||
|
||||
```
|
||||
> walk me through this PR
|
||||
> explain the changes on this branch
|
||||
> create a walkthrough of the authentication feature
|
||||
> deep dive into how the new caching layer works
|
||||
```
|
||||
|
||||
The skill will:
|
||||
1. Research all changed files and git history
|
||||
2. Plan a narrative structure
|
||||
3. Generate a self-contained HTML file
|
||||
4. Open it in your browser
|
||||
|
||||
The output file lands in the project root as `{topic}-walkthrough.html`.
|
||||
|
||||
## How It Works
|
||||
|
||||
The skill instructs Claude Code to follow a structured workflow:
|
||||
|
||||
1. **Research** — Read all changed files (not just diffs), trace data flow, understand the full context
|
||||
2. **Plan** — Structure the walkthrough as a teaching document with sections for architecture, implementation, wiring, tradeoffs, and extension patterns
|
||||
3. **Write** — Generate a self-contained HTML page using the design system from the reference template
|
||||
4. **Deliver** — Save and open in the browser
|
||||
|
||||
The reference template (`templates/reference-walkthrough.html`) serves as both a design system reference and a content example. Claude Code reads it before generating each walkthrough to match the aesthetic and component patterns.
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples/` directory for sample walkthrough outputs.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -1,163 +0,0 @@
|
||||
---
|
||||
name: pr-walkthrough
|
||||
description: Generate a detailed, pedagogical HTML walkthrough of a PR or feature branch. Use when the user wants to understand, document, or share how a PR works — covering architecture, data flow, design decisions, tradeoffs, and extension patterns. Triggers on requests like "walk me through this PR", "explain this branch", "write up how this feature works", "create a walkthrough".
|
||||
---
|
||||
|
||||
# PR Walkthrough Generator
|
||||
|
||||
Generate a self-contained HTML page that deeply explains a PR or feature branch — not just what changed, but *why*, *how it connects*, and *what patterns it establishes* for future work.
|
||||
|
||||
## When to Use
|
||||
|
||||
- User asks to understand or explain a PR / branch / feature
|
||||
- User wants a shareable document about how something works
|
||||
- User wants to document architecture decisions for the team
|
||||
- User says "walkthrough", "deep dive", "explain this PR", "write up how this works"
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Research the PR Thoroughly
|
||||
|
||||
Read **everything** before writing a single line of HTML. The quality of the walkthrough depends entirely on depth of understanding.
|
||||
|
||||
**Gather context in parallel:**
|
||||
- `git log --oneline main..HEAD` — commit history and narrative arc
|
||||
- `git diff --stat main..HEAD` — scope and affected areas
|
||||
- `git diff --name-only main..HEAD` — file list for categorization
|
||||
- `gh pr view --json title,body,url` — PR description if available
|
||||
|
||||
**Read all changed files that matter.** Not just the diff — read the full files to understand context. Prioritize:
|
||||
1. New files (these ARE the feature)
|
||||
2. Interface/type definitions (these define the contracts)
|
||||
3. Wiring/glue code (how things connect)
|
||||
4. UI components (what users see)
|
||||
5. Configuration and infrastructure changes
|
||||
|
||||
**Understand the threading.** Trace data flow from entry point to leaf. For backend features, trace from route handler through service layers to the actual operation. For AI/tool features, trace from registration through execution context to the tool itself.
|
||||
|
||||
### 2. Plan the Narrative
|
||||
|
||||
Structure as a **teaching document**, not a changelog. The reader should understand:
|
||||
|
||||
1. **The big picture** — What problem does this solve? What's the before/after?
|
||||
2. **The architecture** — What are the layers? How do they relate?
|
||||
3. **The key abstraction** — What's the central interface or pattern?
|
||||
4. **The implementation** — Walk through each component, building understanding incrementally
|
||||
5. **The wiring** — How do the pieces connect at runtime? Trace the full path.
|
||||
6. **The UX** — What does the user see and do?
|
||||
7. **The extension story** — How would you add a similar feature? What's the recipe?
|
||||
8. **The tradeoffs** — What alternatives existed? Why was this approach chosen?
|
||||
9. **The limitations** — What doesn't it do yet? What are natural next steps?
|
||||
|
||||
Not every section applies to every PR. Adapt the structure. A pure refactoring PR needs different sections than a new feature PR.
|
||||
|
||||
### 3. Write the HTML
|
||||
|
||||
Read the reference template at `./templates/reference-walkthrough.html` before generating. It demonstrates the exact aesthetic, component library, and patterns to use.
|
||||
|
||||
**Aesthetic direction: Editorial / Technical Paper.** The walkthrough should feel like a well-typeset technical article — a mix of long-form explanation, annotated code, and clear diagrams. Think: a Stripe engineering blog post or a well-written RFC.
|
||||
|
||||
#### Design System
|
||||
|
||||
**Typography:**
|
||||
- Display font: `Instrument Serif` (headings) — elegant, editorial feel
|
||||
- Body font: `Source Serif 4` — readable long-form text
|
||||
- Mono font: `DM Mono` — code, labels, metadata
|
||||
- Load all from Google Fonts
|
||||
|
||||
**Color palette (warm paper theme):**
|
||||
```css
|
||||
--ink: #1a1a18; /* primary text */
|
||||
--paper: #f5f0e8; /* page background */
|
||||
--paper-warm: #ede7db; /* secondary surfaces */
|
||||
--accent: #c23616; /* emphasis, new items, key callouts */
|
||||
--blue: #2d5f8a; /* insights, links */
|
||||
--green: #3a7d44; /* patterns, success, strings */
|
||||
--purple: #6a4c93; /* tradeoffs */
|
||||
--orange: #d4820a; /* warnings, keywords */
|
||||
--gray: #8a8578; /* secondary text, metadata */
|
||||
```
|
||||
|
||||
Each semantic color has a `-dim` variant at ~15% opacity for backgrounds.
|
||||
|
||||
#### Component Library
|
||||
|
||||
Use these components (all demonstrated in the reference template):
|
||||
|
||||
**Section structure:**
|
||||
- Section number as small mono kicker (`01`, `02`, ...)
|
||||
- `h2` with display font and bottom border
|
||||
- `h3` for subsections, `h4` for small mono labels
|
||||
|
||||
**Code blocks (`<pre>`):**
|
||||
- Dark background (`var(--ink)`), left accent border
|
||||
- File label badge in top-right corner (`.file-label`)
|
||||
- Syntax highlighting via span classes: `.keyword`, `.string`, `.type`, `.fn`, `.comment`
|
||||
- Keep code excerpts focused — show the essential 5-15 lines, not the whole file
|
||||
|
||||
**Diagrams (`.diagram-container`):**
|
||||
- White background with thin border
|
||||
- Rainbow gradient top strip (accent → blue → green → purple)
|
||||
- Mono label at top
|
||||
|
||||
**Flow diagrams (`.flow`):**
|
||||
- Vertical flow with `.flow-row` containing `.flow-box` elements
|
||||
- Boxes color-coded by role: `.accent`, `.blue`, `.green`, `.purple`, `.orange`, `.filled`
|
||||
- Arrow separators (`.flow-arrow`) and notes (`.flow-note`) between rows
|
||||
- Each box can have a `<small>` subtitle
|
||||
|
||||
**Layer diagrams (`.layer-stack`):**
|
||||
- Horizontal rows with label + items
|
||||
- Items tagged `.new` get a red "NEW" badge
|
||||
|
||||
**Callouts (`.callout`):**
|
||||
- Four types: `.insight` (blue), `.warning` (orange), `.pattern` (green), `.tradeoff` (purple)
|
||||
- Mono label at top, then explanation text
|
||||
- Use for key takeaways that deserve visual emphasis
|
||||
|
||||
**Comparison tables (`.comparison`):**
|
||||
- Mono uppercase headers
|
||||
- First column as `.label-cell` (bold mono)
|
||||
- Clean bottom-border rows
|
||||
|
||||
**File trees (`.file-tree`):**
|
||||
- Mono text with `.dir`, `.file`, `.new-file` classes
|
||||
- Indent levels via `.indent`, `.indent-2`
|
||||
|
||||
**Table of contents (`.toc`):**
|
||||
- Two-column layout with numbered entries
|
||||
- "Contents" label as positioned pseudo-element
|
||||
|
||||
#### Content Principles
|
||||
|
||||
**Show real code, not pseudocode.** Extract actual code from the PR files. Trim to the essential lines. Add syntax highlighting spans.
|
||||
|
||||
**Annotate, don't just describe.** After a code block, explain *why* it's designed that way, not just *what* it does.
|
||||
|
||||
**Use diagrams for flow, text for reasoning.** Flow diagrams show *how things connect*. Prose explains *why they connect that way*.
|
||||
|
||||
**Callouts for key insights.** If you find yourself writing "importantly" or "note that" in prose, extract it into a callout box instead.
|
||||
|
||||
**Be opinionated about tradeoffs.** Don't just list alternatives — explain why the chosen approach was right for this context, and when the alternative would be better.
|
||||
|
||||
### 4. Deliver
|
||||
|
||||
**Output location:** Write to the project root as `{topic}-walkthrough.html` (e.g., `github-integration-walkthrough.html`).
|
||||
|
||||
**Open in browser:**
|
||||
- macOS: `open path/to/walkthrough.html`
|
||||
|
||||
**Tell the user:**
|
||||
- The file path
|
||||
- A summary of sections covered
|
||||
- That the file is in the repo root and they may want to gitignore or delete it
|
||||
|
||||
## Quality Checks
|
||||
|
||||
Before delivering, verify:
|
||||
- **Completeness:** Does every new file/concept get explained? Did you miss any layer?
|
||||
- **Flow:** Can someone unfamiliar with the codebase follow the narrative from section 1 to the end?
|
||||
- **Code accuracy:** Are code excerpts real (from the actual files), not invented?
|
||||
- **Diagram clarity:** Do flow diagrams actually trace the real runtime path?
|
||||
- **Tradeoff depth:** Did you go beyond "we could have done X instead" to explain *why* the choice was made?
|
||||
- **Extension recipe:** Could a developer follow your recipe to add a similar feature?
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
theme = Catppuccin Latte
|
||||
font-family = JetBrainsMono Nerd Font Mono
|
||||
|
||||
# https://pi.dev/docs/latest/terminal-setup
|
||||
keybind = alt+backspace=text:\x1b\x7f
|
||||
@@ -18,7 +18,8 @@ pnpm = "latest"
|
||||
"npm:@anthropic-ai/sandbox-runtime" = "latest"
|
||||
"npm:typescript-language-server" = "latest"
|
||||
"npm:typescript" = "latest"
|
||||
"github:Satty-org/Satty" = "0.20.1"
|
||||
"github:Satty-org/Satty" = "0.21.1"
|
||||
"github:DarthSim/overmind" = "latest"
|
||||
"github:F1bonacc1/process-compose" = "latest"
|
||||
"github:modem-dev/hunk" = "latest"
|
||||
"npm:@earendil-works/pi-coding-agent" = "latest"
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
{
|
||||
"LazyVim": { "branch": "main", "commit": "c10948c50b18fae7f256433afdef09e432410480" },
|
||||
"SchemaStore.nvim": { "branch": "main", "commit": "735540a7602bffd1b07deab7d508affb57b78c64" },
|
||||
"SchemaStore.nvim": { "branch": "main", "commit": "6ff1f21b2e2b77ec59f7433ce2d9fbc052d908ac" },
|
||||
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
|
||||
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
|
||||
"catppuccin": { "branch": "main", "commit": "49a926655a2f5579e9c276470fc300baaa49e524" },
|
||||
"code-review.nvim": { "branch": "main", "commit": "ed91462e20bd08c3be71efb11a4a7d00459f0b47" },
|
||||
"catppuccin": { "branch": "main", "commit": "e068ab5f8261f23f6f71ffd8791ae40315b77b9c" },
|
||||
"conform.nvim": { "branch": "master", "commit": "619363c30309d29ffa631e67c8183f2a72caa373" },
|
||||
"diffview.nvim": { "branch": "main", "commit": "c65ddb4724cc772a77e9eca1478bff451867b2e2" },
|
||||
"diffview.nvim": { "branch": "main", "commit": "bcf4b62b4acc36a7c3d19e423713a220c838a668" },
|
||||
"flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||
"gitsigns.nvim": { "branch": "main", "commit": "42d6aed4e94e0f0bbced16bbdcc42f57673bd75e" },
|
||||
"grug-far.nvim": { "branch": "main", "commit": "c995bbacf8229dc096ec1c3d60f8531059c86c1b" },
|
||||
"grug-far.nvim": { "branch": "main", "commit": "c69859c1d5427ab5fc7ed12380ab521b4e336691" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
|
||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
|
||||
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "21c5b3ebeaa0412e28096bb0701434c51c1fbf76" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "47059d71b42d74b0a1e9f61c1d99d301039c3b5b" },
|
||||
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
|
||||
"mini.ai": { "branch": "main", "commit": "4511b3481707c1d021485475d34f2ed2a50bf47b" },
|
||||
"mini.diff": { "branch": "main", "commit": "05be51814a718e74244829754a2a900a430a8d8b" },
|
||||
"mini.icons": { "branch": "main", "commit": "ac38c983aed0a2bd32a65ca3e2348e12e58ca292" },
|
||||
"mini.pairs": { "branch": "main", "commit": "30cf2f01c4aaa2033db67376b9924fa2442c05d6" },
|
||||
"mini.surround": { "branch": "main", "commit": "d401c3856585473693e5ce9ec4c8e5b608b4c0fe" },
|
||||
"mini.ai": { "branch": "main", "commit": "cb20f298ebf5ae91924cd0c6c310712de2ef4086" },
|
||||
"mini.diff": { "branch": "main", "commit": "0743d26bd858ebe32efcf5c86a91a422a000f273" },
|
||||
"mini.icons": { "branch": "main", "commit": "24dbea2195c477e57d581215839a6ab915f34b14" },
|
||||
"mini.pairs": { "branch": "main", "commit": "fd150ac39b78e6a2286f5138e472b7dc7eba43b9" },
|
||||
"mini.surround": { "branch": "main", "commit": "a2f644f3759edd3d3f8b6a6d55378408bfe6d290" },
|
||||
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
|
||||
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||
"nvim-ansible": { "branch": "main", "commit": "c7f595d568b588942d4d0c37b5cd6cae3764a148" },
|
||||
"nvim-lint": { "branch": "master", "commit": "4b7957daf4b81eb578114bd6fcf20b6f5a2b59e8" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "a683e0ddf0cf64c6cd689e18ffb480ade3c162b7" },
|
||||
"nvim-lint": { "branch": "master", "commit": "a219b2c9e5b4765e5c845aba119dad55806fcaf1" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "292f44408498103c47996ff5c18fd366293840d8" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
|
||||
"nvim-treesitter-textobjects": { "branch": "main", "commit": "851e865342e5a4cb1ae23d31caf6e991e1c99f1e" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
|
||||
"octo.nvim": { "branch": "master", "commit": "7fed87415c401954f73401bbed0fd736b9611e7c" },
|
||||
"octo.nvim": { "branch": "master", "commit": "b9a73e167f851a98d8f29d62658d3640bb8a7314" },
|
||||
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "5adf0895310c1904e5abfaad40a2baad7fe44a07" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" },
|
||||
"sidekick.nvim": { "branch": "main", "commit": "208e1c5b8170c01fd1d07df0139322a76479b235" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
|
||||
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
|
||||
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },
|
||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||
"ts-comments.nvim": { "branch": "main", "commit": "123a9fb12e7229342f807ec9e6de478b1102b041" },
|
||||
"ts-comments.nvim": { "branch": "main", "commit": "a59d6092213447450191122c9346f309161504cb" },
|
||||
"vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" },
|
||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
return {
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
opts = {
|
||||
spec = {
|
||||
{ "<leader>r", group = "review" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
dir = "/home/tgrosinger/code/claude-review",
|
||||
cmd = "ClaudeReview",
|
||||
dependencies = {
|
||||
"dlyongemallo/diffview.nvim",
|
||||
},
|
||||
opts = {},
|
||||
},
|
||||
}
|
||||
@@ -16,57 +16,59 @@
|
||||
-- Inside the comment input window: <C-s> submits, <Esc> or q cancels.
|
||||
-- (Terminals can't distinguish <C-CR> from <CR>, so we bind <C-s> instead.)
|
||||
|
||||
return {
|
||||
{
|
||||
"folke/which-key.nvim",
|
||||
opts = {
|
||||
spec = {
|
||||
{ "<leader>r", group = "review" },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"choplin/code-review.nvim",
|
||||
cmd = {
|
||||
"CodeReviewComment",
|
||||
"CodeReviewShowComment",
|
||||
"CodeReviewList",
|
||||
"CodeReviewPreview",
|
||||
"CodeReviewSave",
|
||||
"CodeReviewCopy",
|
||||
"CodeReviewClear",
|
||||
"CodeReviewDeleteComment",
|
||||
},
|
||||
keys = {
|
||||
{ "<leader>rc", mode = { "n", "v" }, desc = "Code review: add comment" },
|
||||
{ "<leader>rp", desc = "Code review: preview" },
|
||||
{ "<leader>ry", desc = "Code review: copy to clipboard" },
|
||||
{ "<leader>rs", desc = "Code review: show at cursor" },
|
||||
{ "<leader>rl", desc = "Code review: list comments" },
|
||||
{ "<leader>rd", desc = "Code review: delete at cursor" },
|
||||
{ "<leader>rx", desc = "Code review: clear all" },
|
||||
{ "<leader>rw", desc = "Code review: save to file" },
|
||||
},
|
||||
opts = {
|
||||
ui = {
|
||||
input_window = {
|
||||
title = " Add Comment (C-s to submit) ",
|
||||
},
|
||||
},
|
||||
output = {
|
||||
-- Flat format optimized for pasting into AI assistants like Claude Code:
|
||||
-- path/to/file.lua:L42: comment text
|
||||
format = "minimal",
|
||||
},
|
||||
},
|
||||
init = function()
|
||||
vim.api.nvim_create_autocmd("User", {
|
||||
pattern = "CodeReviewInputEnter",
|
||||
callback = function(ev)
|
||||
local funcs = require("code-review").get_input_buffer_functions(ev.data.buf)
|
||||
vim.keymap.set({ "i", "n" }, "<C-s>", funcs.submit, { buffer = ev.data.buf })
|
||||
end,
|
||||
})
|
||||
end,
|
||||
},
|
||||
}
|
||||
return {}
|
||||
|
||||
-- return {
|
||||
-- {
|
||||
-- "folke/which-key.nvim",
|
||||
-- opts = {
|
||||
-- spec = {
|
||||
-- { "<leader>r", group = "review" },
|
||||
-- },
|
||||
-- },
|
||||
-- },
|
||||
-- {
|
||||
-- "choplin/code-review.nvim",
|
||||
-- cmd = {
|
||||
-- "CodeReviewComment",
|
||||
-- "CodeReviewShowComment",
|
||||
-- "CodeReviewList",
|
||||
-- "CodeReviewPreview",
|
||||
-- "CodeReviewSave",
|
||||
-- "CodeReviewCopy",
|
||||
-- "CodeReviewClear",
|
||||
-- "CodeReviewDeleteComment",
|
||||
-- },
|
||||
-- keys = {
|
||||
-- { "<leader>rc", mode = { "n", "v" }, desc = "Code review: add comment" },
|
||||
-- { "<leader>rp", desc = "Code review: preview" },
|
||||
-- { "<leader>ry", desc = "Code review: copy to clipboard" },
|
||||
-- { "<leader>rs", desc = "Code review: show at cursor" },
|
||||
-- { "<leader>rl", desc = "Code review: list comments" },
|
||||
-- { "<leader>rd", desc = "Code review: delete at cursor" },
|
||||
-- { "<leader>rx", desc = "Code review: clear all" },
|
||||
-- { "<leader>rw", desc = "Code review: save to file" },
|
||||
-- },
|
||||
-- opts = {
|
||||
-- ui = {
|
||||
-- input_window = {
|
||||
-- title = " Add Comment (C-s to submit) ",
|
||||
-- },
|
||||
-- },
|
||||
-- output = {
|
||||
-- -- Flat format optimized for pasting into AI assistants like Claude Code:
|
||||
-- -- path/to/file.lua:L42: comment text
|
||||
-- format = "minimal",
|
||||
-- },
|
||||
-- },
|
||||
-- init = function()
|
||||
-- vim.api.nvim_create_autocmd("User", {
|
||||
-- pattern = "CodeReviewInputEnter",
|
||||
-- callback = function(ev)
|
||||
-- local funcs = require("code-review").get_input_buffer_functions(ev.data.buf)
|
||||
-- vim.keymap.set({ "i", "n" }, "<C-s>", funcs.submit, { buffer = ev.data.buf })
|
||||
-- end,
|
||||
-- })
|
||||
-- end,
|
||||
-- },
|
||||
-- }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- Restore <C-e> to its default scroll behavior in Octo review/PR buffers.
|
||||
-- Octo binds <C-e> to copy_sha, which errors "Not a pull request buffer" in
|
||||
-- diff buffers instead of scrolling. Blanking the lhs makes Octo skip the
|
||||
-- mapping (see octo.utils.is_blank / apply_mappings), so <C-e> scrolls again.
|
||||
return {
|
||||
"pwntester/octo.nvim",
|
||||
opts = {
|
||||
mappings = {
|
||||
review_diff = {
|
||||
copy_sha = { lhs = "" },
|
||||
},
|
||||
pull_request = {
|
||||
copy_sha = { lhs = "" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
return {
|
||||
"folke/snacks.nvim",
|
||||
opts = {
|
||||
picker = {
|
||||
sources = {
|
||||
explorer = {
|
||||
hidden = true,
|
||||
ignored = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -93,7 +93,7 @@ input "2362:628:PIXA3854:00_093A:0274_Touchpad" {
|
||||
bindsym $mod+Equal exec /home/tgrosinger/.config/rofi/scripts/qalc.sh
|
||||
|
||||
# Lazygit
|
||||
bindsym $mod+g exec alacritty --title Floating-Lazygit --command /home/linuxbrew/.linuxbrew/bin/lazygit; grab_focus; floating enable
|
||||
bindsym $mod+g exec $term --title Floating-Lazygit --command /home/linuxbrew/.linuxbrew/bin/lazygit; grab_focus; floating enable
|
||||
for_window [title="Floating-Lazygit"] floating enable
|
||||
for_window [title="Floating-Lazygit"] resize set 1800 1200
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"bashAllowPatterns": []
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
/**
|
||||
* Approval Gate Extension
|
||||
*
|
||||
* Prompts before mutating/dangerous tool calls execute.
|
||||
*
|
||||
* For `edit` calls, renders a rich diff preview (with surrounding file
|
||||
* context) by reusing pi's built-in edit tool renderer inside a custom
|
||||
* approval modal. For `bash`/`write`, falls back to a text confirm dialog.
|
||||
*
|
||||
* Config: ~/.pi/agent/approval-gate.json
|
||||
* {
|
||||
* "bashAllowPatterns": ["^pwd$"]
|
||||
* }
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
createEditToolDefinition,
|
||||
getAgentDir,
|
||||
type EditToolInput,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import {
|
||||
Box,
|
||||
type Component,
|
||||
Key,
|
||||
matchesKey,
|
||||
Spacer,
|
||||
Text,
|
||||
} from "@earendil-works/pi-tui";
|
||||
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
interface ApprovalGateConfig {
|
||||
bashAllowPatterns?: unknown;
|
||||
}
|
||||
|
||||
interface LoadedConfig {
|
||||
bashAllowPatterns: RegExp[];
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
const BLOCKED_REASON = "Blocked by approval-gate";
|
||||
const STATUS_KEY = "approval-gate";
|
||||
const CONFIG_FILE = "approval-gate.json";
|
||||
|
||||
function truncate(value: string, maxLength = 1200): string {
|
||||
if (value.length <= maxLength) return value;
|
||||
return `${value.slice(0, maxLength)}\n… truncated ${value.length - maxLength} character(s)`;
|
||||
}
|
||||
|
||||
function formatBytes(value: string): string {
|
||||
const bytes = Buffer.byteLength(value, "utf8");
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function loadConfig(): LoadedConfig {
|
||||
const configPath = join(getAgentDir(), CONFIG_FILE);
|
||||
const warnings: string[] = [];
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
let parsed: ApprovalGateConfig;
|
||||
try {
|
||||
parsed = JSON.parse(readFileSync(configPath, "utf8")) as ApprovalGateConfig;
|
||||
} catch (error) {
|
||||
warnings.push(`Failed to parse ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
if (parsed.bashAllowPatterns === undefined) {
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
if (!Array.isArray(parsed.bashAllowPatterns)) {
|
||||
warnings.push(`${configPath}: bashAllowPatterns must be an array of regex pattern strings`);
|
||||
return { bashAllowPatterns: [], warnings };
|
||||
}
|
||||
|
||||
const bashAllowPatterns: RegExp[] = [];
|
||||
for (const pattern of parsed.bashAllowPatterns) {
|
||||
if (typeof pattern !== "string") {
|
||||
warnings.push(`${configPath}: skipped non-string bash allow pattern`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
bashAllowPatterns.push(new RegExp(pattern));
|
||||
} catch (error) {
|
||||
warnings.push(
|
||||
`${configPath}: skipped invalid regex ${JSON.stringify(pattern)}: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { bashAllowPatterns, warnings };
|
||||
}
|
||||
|
||||
function updateStatus(ctx: ExtensionContext, enabled: boolean): void {
|
||||
if (!ctx.hasUI) return;
|
||||
ctx.ui.setStatus(STATUS_KEY, `approvals: ${enabled ? "✓" : "✗"}`);
|
||||
}
|
||||
|
||||
function isAllowedBashCommand(command: string, patterns: RegExp[]): boolean {
|
||||
const trimmed = command.trim();
|
||||
return patterns.some((pattern) => pattern.test(trimmed));
|
||||
}
|
||||
|
||||
function summarizeToolCall(toolName: string, input: Record<string, unknown>): string {
|
||||
if (toolName === "bash") {
|
||||
const command = typeof input.command === "string" ? input.command : JSON.stringify(input, null, 2);
|
||||
return `Command:\n\n${truncate(command)}`;
|
||||
}
|
||||
|
||||
if (toolName === "edit") {
|
||||
const path = typeof input.path === "string" ? input.path : "unknown";
|
||||
const edits = Array.isArray(input.edits) ? input.edits : [];
|
||||
const editSummaries = edits.slice(0, 3).map((edit, index) => {
|
||||
if (!edit || typeof edit !== "object") return `Edit ${index + 1}: <unrecognized edit block>`;
|
||||
const block = edit as { oldText?: unknown; newText?: unknown };
|
||||
const oldText = typeof block.oldText === "string" ? block.oldText : "<missing oldText>";
|
||||
const newText = typeof block.newText === "string" ? block.newText : "<missing newText>";
|
||||
return [
|
||||
`Edit ${index + 1}:`,
|
||||
` old (${formatBytes(oldText)}): ${truncate(oldText, 500)}`,
|
||||
` new (${formatBytes(newText)}): ${truncate(newText, 500)}`,
|
||||
].join("\n");
|
||||
});
|
||||
|
||||
const omitted = edits.length > editSummaries.length ? `\n\n… omitted ${edits.length - editSummaries.length} edit block(s)` : "";
|
||||
return [`Path: ${path}`, `Edit blocks: ${edits.length}`, "", ...editSummaries].join("\n") + omitted;
|
||||
}
|
||||
|
||||
if (toolName === "write") {
|
||||
const path = typeof input.path === "string" ? input.path : "unknown";
|
||||
const content = typeof input.content === "string" ? input.content : undefined;
|
||||
if (content === undefined) {
|
||||
return `Path: ${path}\nContent size: unknown`;
|
||||
}
|
||||
|
||||
return `Path: ${path}\nContent size: ${formatBytes(content)}\n\nContent preview:\n${truncate(content)}`;
|
||||
}
|
||||
|
||||
return truncate(JSON.stringify(input, null, 2));
|
||||
}
|
||||
|
||||
interface EditRenderState {
|
||||
callComponent?: Box & {
|
||||
preview?: unknown;
|
||||
previewArgsKey?: string;
|
||||
previewPending?: boolean;
|
||||
settledError?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface EditRenderContext {
|
||||
args: EditToolInput;
|
||||
toolCallId: string;
|
||||
invalidate: () => void;
|
||||
lastComponent: Component | undefined;
|
||||
state: EditRenderState;
|
||||
cwd: string;
|
||||
executionStarted: boolean;
|
||||
argsComplete: boolean;
|
||||
isPartial: boolean;
|
||||
expanded: boolean;
|
||||
showImages: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom approval component for `edit` tool calls.
|
||||
*
|
||||
* Reuses pi's built-in edit tool `renderCall` to render the same contextual
|
||||
* diff preview the user would see in the normal tool-execution row, including
|
||||
* surrounding file context and colored intra-line changes. The preview is
|
||||
* computed asynchronously (reads the target file), so we re-render on
|
||||
* invalidate until it settles.
|
||||
*/
|
||||
class EditApprovalComponent implements Component {
|
||||
private readonly tui: { requestRender: () => void };
|
||||
private readonly theme: { fg: (color: string, text: string) => string; bold: (text: string) => string };
|
||||
private readonly editDef: ReturnType<typeof createEditToolDefinition>;
|
||||
private readonly input: EditToolInput;
|
||||
private readonly cwd: string;
|
||||
private readonly toolCallId: string;
|
||||
private readonly state: EditRenderState = {};
|
||||
private lastComponent: Component | undefined;
|
||||
private readonly header: Text;
|
||||
private readonly footer: Text;
|
||||
private readonly topBorder: DynamicBorder;
|
||||
private readonly bottomBorder: DynamicBorder;
|
||||
private settled = false;
|
||||
|
||||
public onApprove?: () => void;
|
||||
public onReject?: () => void;
|
||||
|
||||
constructor(opts: {
|
||||
tui: { requestRender: () => void };
|
||||
theme: { fg: (color: string, text: string) => string; bold: (text: string) => string };
|
||||
cwd: string;
|
||||
input: EditToolInput;
|
||||
toolCallId: string;
|
||||
}) {
|
||||
this.tui = opts.tui;
|
||||
this.theme = opts.theme;
|
||||
this.cwd = opts.cwd;
|
||||
this.input = opts.input;
|
||||
this.toolCallId = opts.toolCallId;
|
||||
this.editDef = createEditToolDefinition(opts.cwd);
|
||||
|
||||
const editCount = Array.isArray(this.input.edits) ? this.input.edits.length : 0;
|
||||
const headerText = `${this.theme.fg("toolTitle", this.theme.bold("edit"))} ${this.theme.fg("accent", this.input.path)}${this.theme.fg("dim", ` · ${editCount} block(s) · approve?`)}`;
|
||||
this.header = new Text(headerText, 1, 0);
|
||||
this.footer = new Text(this.theme.fg("dim", "y / enter approve · n / esc reject"), 1, 0);
|
||||
const borderFn = (s: string) => this.theme.fg("accent", s);
|
||||
this.topBorder = new DynamicBorder(borderFn);
|
||||
this.bottomBorder = new DynamicBorder(borderFn);
|
||||
}
|
||||
|
||||
private buildContext(): EditRenderContext {
|
||||
return {
|
||||
args: this.input,
|
||||
toolCallId: this.toolCallId,
|
||||
invalidate: () => this.tui.requestRender(),
|
||||
lastComponent: this.lastComponent,
|
||||
state: this.state,
|
||||
cwd: this.cwd,
|
||||
executionStarted: false,
|
||||
argsComplete: true,
|
||||
isPartial: false,
|
||||
expanded: true,
|
||||
showImages: false,
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
private renderEditPreview(width: number): string[] {
|
||||
const renderCall = this.editDef.renderCall;
|
||||
if (!renderCall) return [this.theme.fg("warning", "(diff preview unavailable)")];
|
||||
const component = renderCall(this.input, this.theme as never, this.buildContext() as never);
|
||||
this.lastComponent = component;
|
||||
return component.render(width);
|
||||
}
|
||||
|
||||
render(width: number): string[] {
|
||||
const lines: string[] = [];
|
||||
lines.push(...this.topBorder.render(width));
|
||||
lines.push(...this.header.render(width));
|
||||
lines.push(...this.renderEditPreview(width));
|
||||
lines.push(...new Spacer(1).render(width));
|
||||
lines.push(...this.footer.render(width));
|
||||
lines.push(...this.bottomBorder.render(width));
|
||||
return lines;
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
if (this.settled) return;
|
||||
if (matchesKey(data, Key.enter) || data === "y" || data === "Y") {
|
||||
this.settled = true;
|
||||
this.onApprove?.();
|
||||
} else if (matchesKey(data, Key.escape) || data === "n" || data === "N") {
|
||||
this.settled = true;
|
||||
this.onReject?.();
|
||||
}
|
||||
}
|
||||
|
||||
invalidate(): void {
|
||||
this.header.invalidate();
|
||||
this.footer.invalidate();
|
||||
this.topBorder.invalidate();
|
||||
this.bottomBorder.invalidate();
|
||||
this.lastComponent?.invalidate?.();
|
||||
}
|
||||
}
|
||||
|
||||
function isEditInput(value: Record<string, unknown>): value is EditToolInput {
|
||||
return (
|
||||
typeof value.path === "string" &&
|
||||
Array.isArray(value.edits) &&
|
||||
value.edits.length > 0 &&
|
||||
value.edits.every(
|
||||
(e) => e && typeof e === "object" && typeof (e as { oldText?: unknown }).oldText === "string" && typeof (e as { newText?: unknown }).newText === "string",
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function approveEditCall(input: Record<string, unknown>, ctx: ExtensionContext, toolCallId: string): Promise<boolean> {
|
||||
if (ctx.mode !== "tui" || !isEditInput(input)) {
|
||||
// Non-TUI or malformed input: fall back to text summary confirm.
|
||||
const message = `${summarizeToolCall("edit", input)}\n\nAllow this tool call?`;
|
||||
return ctx.ui.confirm("Approve edit?", message);
|
||||
}
|
||||
|
||||
return ctx.ui.custom<boolean>((tui, theme, _keybindings, done) => {
|
||||
const component = new EditApprovalComponent({
|
||||
tui: tui as { requestRender: () => void },
|
||||
theme: theme as { fg: (color: string, text: string) => string; bold: (text: string) => string },
|
||||
cwd: ctx.cwd,
|
||||
input,
|
||||
toolCallId,
|
||||
});
|
||||
component.onApprove = () => done(true);
|
||||
component.onReject = () => done(false);
|
||||
return component;
|
||||
});
|
||||
}
|
||||
|
||||
export default function approvalGateExtension(pi: ExtensionAPI) {
|
||||
let enabled = true;
|
||||
const config = loadConfig();
|
||||
|
||||
pi.registerCommand("approval-gate", {
|
||||
description: "Turn mutating/dangerous tool approval prompts on or off",
|
||||
handler: async (args, ctx) => {
|
||||
const action = args.trim().toLowerCase();
|
||||
|
||||
if (action === "on") {
|
||||
enabled = true;
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify("approval-gate enabled", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "off") {
|
||||
enabled = false;
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify("approval-gate disabled", "info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "" || action === "status") {
|
||||
updateStatus(ctx, enabled);
|
||||
ctx.ui.notify(`approval-gate is ${enabled ? "enabled" : "disabled"}`, "info");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ui.notify("Usage: /approval-gate [on|off|status]", "warning");
|
||||
},
|
||||
});
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
updateStatus(ctx, enabled);
|
||||
|
||||
for (const warning of config.warnings) {
|
||||
ctx.ui.notify(`approval-gate: ${warning}`, "warning");
|
||||
}
|
||||
});
|
||||
|
||||
pi.on("tool_call", async (event, ctx) => {
|
||||
if (!enabled) return undefined;
|
||||
|
||||
const input = event.input as Record<string, unknown>;
|
||||
const isBash = event.toolName === "bash";
|
||||
const isEdit = event.toolName === "edit";
|
||||
const isWrite = event.toolName === "write";
|
||||
|
||||
if (!isBash && !isEdit && !isWrite) return undefined;
|
||||
|
||||
if (isBash) {
|
||||
const command = typeof input.command === "string" ? input.command : "";
|
||||
if (command && isAllowedBashCommand(command, config.bashAllowPatterns)) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.hasUI) {
|
||||
return { block: true, reason: `${BLOCKED_REASON} (no UI available for confirmation)` };
|
||||
}
|
||||
|
||||
let approved: boolean;
|
||||
if (isEdit) {
|
||||
approved = await approveEditCall(input, ctx, event.toolCallId);
|
||||
} else {
|
||||
approved = await ctx.ui.confirm(
|
||||
`Approve ${event.toolName}?`,
|
||||
`${summarizeToolCall(event.toolName, input)}\n\nAllow this tool call?`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!approved) {
|
||||
return { block: true, reason: BLOCKED_REASON };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"lastChangelogVersion": "0.79.8",
|
||||
"theme": "catppuccin-latte",
|
||||
"defaultProvider": "openai-codex",
|
||||
"defaultModel": "gpt-5.5",
|
||||
"defaultThinkingLevel": "high",
|
||||
"skills": [
|
||||
"~/.claude/skills"
|
||||
],
|
||||
"hideThinkingBlock": true,
|
||||
"enableInstallTelemetry": false
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
||||
"name": "catppuccin-tui-latte",
|
||||
"vars": {
|
||||
"rosewater": "#dc8a78",
|
||||
"flamingo": "#dd7878",
|
||||
"pink": "#ea76cb",
|
||||
"mauve": "#8839ef",
|
||||
"red": "#d20f39",
|
||||
"maroon": "#e64553",
|
||||
"peach": "#fe640b",
|
||||
"yellow": "#df8e1d",
|
||||
"green": "#40a02b",
|
||||
"teal": "#179299",
|
||||
"sky": "#04a5e5",
|
||||
"sapphire": "#209fb5",
|
||||
"blue": "#1e66f5",
|
||||
"lavender": "#7287fd",
|
||||
"text": "#4c4f69",
|
||||
"subtext1": "#5c5f77",
|
||||
"subtext0": "#6c6f85",
|
||||
"overlay2": "#7c7f93",
|
||||
"overlay1": "#8c8fa1",
|
||||
"overlay0": "#9ca0b0",
|
||||
"surface2": "#acb0be",
|
||||
"surface1": "#bcc0cc",
|
||||
"surface0": "#ccd0da",
|
||||
"base": "#eff1f5",
|
||||
"mantle": "#e6e9ef",
|
||||
"crust": "#dce0e8"
|
||||
},
|
||||
"colors": {
|
||||
"accent": "mauve",
|
||||
"border": "surface2",
|
||||
"borderAccent": "lavender",
|
||||
"borderMuted": "surface1",
|
||||
"success": "green",
|
||||
"error": "red",
|
||||
"warning": "yellow",
|
||||
"muted": "subtext0",
|
||||
"dim": "overlay1",
|
||||
"text": "text",
|
||||
"thinkingText": "subtext1",
|
||||
"selectedBg": "surface0",
|
||||
"userMessageBg": "surface0",
|
||||
"userMessageText": "text",
|
||||
"customMessageBg": "mantle",
|
||||
"customMessageText": "text",
|
||||
"customMessageLabel": "mauve",
|
||||
"toolPendingBg": "mantle",
|
||||
"toolSuccessBg": "surface0",
|
||||
"toolErrorBg": "surface0",
|
||||
"toolTitle": "blue",
|
||||
"toolOutput": "overlay2",
|
||||
"mdHeading": "mauve",
|
||||
"mdLink": "blue",
|
||||
"mdLinkUrl": "sapphire",
|
||||
"mdCode": "peach",
|
||||
"mdCodeBlock": "subtext1",
|
||||
"mdCodeBlockBorder": "surface2",
|
||||
"mdQuote": "subtext0",
|
||||
"mdQuoteBorder": "surface2",
|
||||
"mdHr": "surface2",
|
||||
"mdListBullet": "mauve",
|
||||
"toolDiffAdded": "green",
|
||||
"toolDiffRemoved": "red",
|
||||
"toolDiffContext": "subtext0",
|
||||
"syntaxComment": "overlay2",
|
||||
"syntaxKeyword": "mauve",
|
||||
"syntaxFunction": "blue",
|
||||
"syntaxVariable": "maroon",
|
||||
"syntaxString": "green",
|
||||
"syntaxNumber": "peach",
|
||||
"syntaxType": "yellow",
|
||||
"syntaxOperator": "sky",
|
||||
"syntaxPunctuation": "overlay2",
|
||||
"thinkingOff": "surface1",
|
||||
"thinkingMinimal": "surface2",
|
||||
"thinkingLow": "blue",
|
||||
"thinkingMedium": "sapphire",
|
||||
"thinkingHigh": "mauve",
|
||||
"thinkingXhigh": "red",
|
||||
"bashMode": "peach"
|
||||
},
|
||||
"export": {
|
||||
"pageBg": "#eff1f5",
|
||||
"cardBg": "#e6e9ef",
|
||||
"infoBg": "#ccd0da"
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ set-option -g status-interval 2
|
||||
set-option -g renumber-windows on
|
||||
set-option -g history-limit 10000
|
||||
set -s escape-time 0
|
||||
set-option -g detach-on-destroy off
|
||||
set-hook -g session-closed 'choose-tree -Zs'
|
||||
|
||||
### Fix supporting italics
|
||||
set -g default-terminal "tmux-256color"
|
||||
|
||||
@@ -66,6 +66,9 @@ sudo dnf install \
|
||||
socat \
|
||||
wf-recorder
|
||||
|
||||
sudo dnf copr enable scottames/ghostty
|
||||
sudo dnf install ghostty
|
||||
|
||||
# Install global packages managed by mise.
|
||||
mise install
|
||||
|
||||
|
||||
Reference in New Issue
Block a user