{"id":50204547,"url":"https://github.com/michaeldlfx/suiseki","last_synced_at":"2026-05-26T01:00:31.955Z","repository":{"id":360312679,"uuid":"1241466170","full_name":"michaeldlfx/suiseki","owner":"michaeldlfx","description":"a read-only cli built that brings pierre’s diff/tree model layers and shiki themes to the terminal for polished code and diff rendering","archived":false,"fork":false,"pushed_at":"2026-05-25T23:06:17.000Z","size":1026,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-25T23:24:20.021Z","etag":null,"topics":["bun","cli","code-viewer","diff","git","pierre","shiki","syntax-highlighting","terminal","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michaeldlfx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-17T12:30:46.000Z","updated_at":"2026-05-25T23:08:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"0a448155-a2b6-4179-a5dc-abfe2f459390","html_url":"https://github.com/michaeldlfx/suiseki","commit_stats":null,"previous_names":["michaeldlfx/suiseki"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/michaeldlfx/suiseki","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldlfx%2Fsuiseki","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldlfx%2Fsuiseki/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldlfx%2Fsuiseki/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldlfx%2Fsuiseki/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michaeldlfx","download_url":"https://codeload.github.com/michaeldlfx/suiseki/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldlfx%2Fsuiseki/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33499282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-25T14:31:05.219Z","status":"ssl_error","status_checked_at":"2026-05-25T14:31:02.878Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bun","cli","code-viewer","diff","git","pierre","shiki","syntax-highlighting","terminal","typescript"],"created_at":"2026-05-26T01:00:22.923Z","updated_at":"2026-05-26T01:00:31.940Z","avatar_url":"https://github.com/michaeldlfx.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# suiseki\n\n\u003e A terminal renderer for diffs, files, and trees.\n\n![suiseki rendering a git diff in unified view, syntax-highlighted with the pierre-dark theme](assets/diff-unified.png)\n\n![the same diff rendered in split view](assets/diff-split.png)\n\n\n**Pierre** is French for *stone*. This tool builds on [`@pierre/diffs`](https://github.com/pierrecomputer/pierre/tree/main/packages/diffs) for diff parsing and [Shiki](https://shiki.style) for syntax highlighting, and models its file tree on [`@pierre/trees`](https://github.com/pierrecomputer/pierre/tree/main/packages/trees); the Pierre packages are the work of [The Pierre Computer Company](https://diffs.com).\n\n**水石 / suiseki** is the Japanese art of stone appreciation — a contemplative practice, imported from Chinese scholar's-rock tradition and refined in Japan, of viewing naturally-formed stones for their beauty: finding landscapes, figures, and quiet meaning in their shape. The literal reading is *water-stone* — stones polished and revealed by water over time.\n\nThe name is a homage. *Pierre → stone → 水石*. The tool exists to do, for code, what suiseki does for stones: take something rough and naturally-occurring — the raw output of `git diff`, the unformatted text of a source file — polish it, and reveal the form underneath. Made to be looked at.\n\n## Getting started\n\n### Install (prebuilt binary)\n\nNo Bun required, the binary is self-contained:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/michaeldlfx/suiseki/main/scripts/install.sh | sh\n```\n\nThis detects your platform, downloads the matching binary from the latest\n[GitHub release](https://github.com/michaeldlfx/suiseki/releases), verifies its\nSHA-256 checksum, installs it to `~/.suiseki/bin` (alongside your config), adds a\n`sat` symlink for the file and tree viewer, registers that directory on your\n`PATH`, and creates a default config at `~/.suiseki/config.toml` (the same end\nstate as `make init`). The install directory is user-writable, so no `sudo` is\nneeded. Override it with `SUISEKI_INSTALL_DIR`, or pin a version with\n`SUISEKI_VERSION=0.1.0`.\n\nmacOS (x64 and arm64) and Linux (x64 and arm64, glibc) are supported.\n\n### Install with Homebrew\n\n```bash\nbrew install michaeldlfx/suiseki/suiseki\n```\n\n### Install from source\n\n`suiseki` runs on [Bun](https://bun.sh/). If you don't have it yet, install it first:\n\n```bash\nbrew install oven-sh/bun/bun\n```\n\n(or follow the [Bun install guide](https://bun.sh/docs/installation) for other platforms).\n\nThen clone and run the one-command setup:\n\n```bash\ngit clone https://github.com/michaeldlfx/suiseki\ncd suiseki\nmake init\n```\n\n`make init` installs dependencies, builds the `./bin/suiseki` binary (with a `sat` symlink for the file and tree viewer), registers it on your `PATH` (zsh, bash, and fish are supported), and creates a default config at `~/.suiseki/config.toml`.\n\n### Updating\n\nIf you installed a prebuilt binary, update in place:\n\n```bash\nsuiseki upgrade\n```\n\nIt checks [GitHub Releases](https://github.com/michaeldlfx/suiseki/releases) for a newer version, downloads the binary for your\nplatform, verifies its checksum, and replaces the running executable.\n\n### Wire up Git\n\nWith `suiseki` installed, wire it up as your Git diff pager:\n\n```bash\ngit config --global pager.diff 'suiseki'\ngit config --global pager.show 'suiseki'\ngit config --global interactive.diffFilter 'suiseki --color-only'\n```\n\nThat's it. Run `git diff` and enjoy.\n\n\u003e **Tip:** to customise your config, run `suiseki config` for a fully-annotated\n\u003e reference of every option, or edit `~/.suiseki/config.toml` directly.\n\n## Status\n\n`suiseki` renders three things in the terminal: **diffs** (unified and split views with Shiki syntax highlighting, theme-derived diff backgrounds, configurable file and hunk headers, line numbers, and pager support), **files** (`suiseki view` / `sat`, a syntax-highlighted file viewer), and **directory trees** (`sat \u003cdir\u003e`, with git status). It works both as a piped Unix filter and as a Git pager.\n\nThe project is a friendly terminal surface for Pierre's renderer-agnostic packages and Shiki's syntax/theme ecosystem: [`@pierre/diffs`](https://github.com/pierrecomputer/pierre/tree/main/packages/diffs) first, [`@pierre/trees`](https://github.com/pierrecomputer/pierre/tree/main/packages/trees) next, and [Shiki](https://shiki.style) throughout. It is a homage and companion, not a fork or replacement.\n\n## Usage\n\n```bash\n# pipe a diff\ngit diff | suiseki\n\n# review a whole branch or a PR\nsuiseki origin/main...HEAD              # your branch's changes vs main (fetch origin/main first)\ngh pr diff | suiseki                    # the current branch's PR, via the gh CLI (gh pr diff $PR_NUM for a specific PR)\n\n# pass git diff arguments directly\nsuiseki HEAD~1 HEAD\nsuiseki --staged\nsuiseki HEAD~3..HEAD -- src/\n\n# disable the pager\nsuiseki --no-pager HEAD~1\nSUISEKI_NO_PAGER=true git diff | suiseki\n\n# override config for one run\nsuiseki --view split --theme pierre-light HEAD~1\n```\n\n### As a Git pager\n\nUse per-command pager settings so `suiseki` renders diffs without taking over\nevery paged Git command, such as `git log`:\n\n```bash\ngit config --global pager.diff 'suiseki'\ngit config --global pager.show 'suiseki'\ngit config --global interactive.diffFilter 'suiseki --color-only'\n```\n\nOr open `~/.gitconfig` with your editor and set:\n\n```gitconfig\n[pager]\n\tdiff = suiseki\n\tshow = suiseki\n\n[interactive]\n\tdiffFilter = suiseki --color-only\n```\n\nWith that configured, `git diff`, `git show`, and interactive patch selection\n(`git add -p`, `git reset -p`) render through `suiseki`. Plain `git log` keeps\nGit's normal pager output.\n\nThe `--color-only` flag is a separate, line-preserving rendering path:\n`interactive.diffFilter` maps your keystrokes onto hunks by counting lines, so\nit colorizes the diff in place rather than reflowing it with gutters, headers,\nand full-width backgrounds the way the pager view does.\n\n## Viewing files and trees\n\nBeyond diffs, `suiseki view` syntax-highlights a file, prints a directory tree,\nor reads from stdin. The same command is also available as **`sat`**, a short\nname that mirrors `cat` and `bat`: the `s` is for `suiseki`, and `at` echoes\n`cat`/`bat`. It is installed as a symlink to the `suiseki` binary, so\n`sat \u003cpath\u003e` is exactly `suiseki view \u003cpath\u003e`, whether `\u003cpath\u003e` is a file (show\nits contents) or a directory (print its tree).\n\n![sat showing a source file beside its directory tree, with syntax highlighting and line numbers](assets/sat-file.png)\n\n![sat printing a directory tree with the git-status column](assets/sat-tree.png)\n\n```bash\n# view a file: by default (on a wide terminal) it is shown beside its directory\n# tree, highlighted and revealed in the tree\nsat src/render/diff.ts\nsuiseki view src/render/diff.ts\n\n# turn the tree off for a plain file view\nsat --with-tree=false src/render/diff.ts\n\n# print a directory tree with git status\nsat .\nsat src/\nsuiseki view src/\n\n# read a file from stdin (plain, no tree)\ncat src/cli.ts | sat\nsat - \u003c src/cli.ts\n```\n\nA directory argument prints a tree. Entries come from `git ls-files` inside a\nrepository, so `.gitignore` is honored for free, or from a filesystem walk\noutside one. Directories are marked with a `▾` glyph, and a git-status column\n(added, modified, deleted, renamed, untracked) sits on the left, rolled up to\nparent directories. The tree and viewer flags:\n\n- `--gitignored=\u003chidden|collapsed|expanded\u003e`: how gitignored dirs (`node_modules`,\n  `dist`) appear. Default **collapsed** (`[view].gitignored`): shown as a single\n  `▸ node_modules/` entry, not drilled into. `hidden` omits them, `expanded` shows\n  them in full. `sat \u003cignored-dir\u003e` always shows that directory's own contents.\n- `--hidden` / `--no-hidden`: show or hide dotfiles. On by default (`[view].hidden`).\n- `--no-icons`: hide the `▾` directory glyphs.\n- `--no-git-status`: hide the git-status column.\n- `--with-tree` / `-t`: show a file beside its directory tree. This is **on by\n  default** (`[view].with-tree`); turn it off for one run with\n  `--with-tree=false`, or set `[view].with-tree = false` to default to a plain\n  file view. The sidebar only renders on an interactive terminal: piped or\n  redirected output, terminals below 100 columns, and stdin input all fall back\n  to a clean full-width file view. The tree sits on the left by default; set\n  `[view].with-tree-side = \"right\"` to flip it.\n\nFile viewing reuses the diff renderer's machinery: themes, line numbers, the\nlarge-file plaintext fallback, `--no-color` / `NO_COLOR`, and the pager. Binary\nfiles are detected and skipped rather than dumped as garbage. Run `suiseki view\n--help` for the full option list.\n\n## Configuration\n\n`suiseki` resolves configuration from (highest precedence first):\n\n1. CLI flags\n2. Environment variables (`SUISEKI_*`)\n3. Nearest `.suiseki.toml` found by walking up from the current directory\n4. `$SUISEKI_CONFIG_DIR/config.toml`\n5. `$XDG_CONFIG_HOME/suiseki/config.toml` (defaults to `~/.config/suiseki/config.toml`)\n6. `~/.suiseki/config.toml`\n\nPer-repo `.suiseki.toml` files merge on top of user config.\n\n### Config reference\n\nRun `suiseki config` to print a fully-annotated reference with every option,\nits valid values, default, and corresponding environment variable:\n\n```bash\nsuiseki config\n```\n\nTo create `~/.suiseki/config.toml` pre-filled with annotated defaults:\n\n```bash\nsuiseki config --init\n```\n\n### Full config with defaults\n\n```toml\n[pierre]\nview = \"unified\"             # SUISEKI_PIERRE_VIEW (unified | split)\nline-numbers = true          # SUISEKI_PIERRE_LINE_NUMBERS\nchange-indicator = \"sign\"    # SUISEKI_PIERRE_CHANGE_INDICATOR (sign | bar | background)\ndiff-background = true       # SUISEKI_PIERRE_DIFF_BACKGROUND\nfile-header = true           # SUISEKI_PIERRE_FILE_HEADER\nhunk-header = \"none\"         # SUISEKI_PIERRE_HUNK_HEADER (full | none)\nword-diff = \"word-alt\"       # SUISEKI_PIERRE_WORD_DIFF (word-alt | word | char | none)\nmax-line-diff-length = 1000  # SUISEKI_PIERRE_MAX_LINE_DIFF_LENGTH\n\n[shiki]\ntheme = \"pierre-dark\"        # SUISEKI_SHIKI_THEME (any bundled Shiki theme or Pierre theme)\nmax-line-length = 10000      # SUISEKI_SHIKI_MAX_LINE_LENGTH\nmax-file-lines = 10000       # SUISEKI_SHIKI_MAX_FILE_LINES\n\n[view]\ngitignored = \"collapsed\"     # SUISEKI_VIEW_GITIGNORED (hidden | collapsed | expanded)\nhidden = true                # SUISEKI_VIEW_HIDDEN (show dotfiles)\nwith-tree = true             # SUISEKI_VIEW_WITH_TREE (sat/view: show the directory tree beside the file)\nwith-tree-side = \"left\"      # SUISEKI_VIEW_WITH_TREE_SIDE (which side the tree sits on: left | right)\n```\n\nEvery config key can be overridden with a matching CLI flag, such as\n`--view split`, `--word-diff none`, `--no-line-numbers`, or\n`--max-line-length 5000`. Run `suiseki --help` for the full list.\n\n`max-file-lines` is a performance guard. When a single file has more added +\nremoved lines than this, the `-N +M` total shown in its header, it renders as\nplaintext (no syntax highlighting) while keeping diff backgrounds and gutters. A\ndim note in the file header marks any file that falls back. See\n[Performance](#performance).\n\n### Themes\n\nRun `suiseki themes` to list all available themes. Built-in Pierre themes:\n\n- `pierre-dark` (default)\n- `pierre-light`\n- `pierre-dark-vibrant`\n- `pierre-light-vibrant`\n\nAny [Shiki bundled theme](https://shiki.style/themes) is also accepted (e.g. `github-dark`, `nord`, `dracula`).\n\nCustom themes can be placed as `.json` VSCode-compatible theme files in `~/.suiseki/themes/`. The filename without `.json` becomes the theme name.\n\n## Performance\n\n`suiseki` renders and emits one file at a time, so output starts streaming to\nstdout immediately and peak memory stays bounded by the largest file rather than\nthe whole diff. Typical files highlight in well under a second.\n\nRender time is dominated by syntax tokenization, so a single very large file\nwould otherwise be slow. `shiki.max-file-lines` (default `10000`) guards against\nthat: a file with more added + removed lines than this, its `-N +M` header\ntotal, renders as plaintext instead of paying for grammar highlighting on a file\nyou are usually scrolling past anyway, such as lockfiles, generated bundles, or\nsnapshots. Diff backgrounds, gutters, and a dim note in its header are kept.\nAdjust it in config or with `--max-file-lines`.\n\n## Development\n\n### Prerequisites\n\n- [Bun](https://bun.sh/) (runtime, package manager, test runner, compiler)\n\n### Make targets\n\nRun `make` or `make help` to see all available targets:\n\n| Target | Description |\n|--------|-------------|\n| `make init` | First-time setup: install deps, build binary, register on PATH, create default config |\n| `make setup` | Build binary, register on PATH, and create default config |\n| `make help` | Show all available targets |\n| `make install` | Install dependencies |\n| `make install-frozen` | Install dependencies from lockfile |\n| `make run` | Run project as TypeScript sources |\n| `make build` | Build the `./bin/suiseki` binary |\n| `make release` | Cross-compile all release targets into `dist/` with checksums |\n| `make start` | Run the compiled binary |\n| `make clean` | Remove build artifacts and caches |\n| `make test` | Run all tests with coverage |\n| `make check` | Type check + lint/format (auto-fix) |\n| `make check-ci` | Type check + lint (no auto-fix, for CI) |\n| `make format` | Format code with Biome |\n\n### Releasing\n\nReleases are CI-only. Every PR into `main` carries exactly one semver label\n(`patch`, `minor`, or `major`), or the `documentation` label for a docs-only PR\nthat does not release. The `release guard` check enforces this and forbids\nhand-editing the `package.json` version (the label drives the bump); a\n`documentation` PR must not also carry a semver label or change anything under\n`src/`. On\npush to `main`, `main-branch-workflow.yaml` runs one pipeline: **build and\nverify** (checks + tests + build) → **plan** (read the merged PR's label) →\n**tag** (bump `package.json`, tag `v\u003cversion\u003e`, push) → **publish** (build all\ntargets, create the GitHub Release with checksums). The release stages run only\nwhen the commit's PR carried a semver label, so direct pushes are just verified.\n\n## Tech Stack\n\n- **Bun** + TypeScript for runtime, tests, and single-binary compilation.\n- **[Shiki](https://shiki.style)** for syntax tokenization and theme compatibility.\n- **[`@pierre/diffs`](https://github.com/pierrecomputer/pierre/tree/main/packages/diffs)** for diff parsing and iteration.\n- **[`@pierre/trees`](https://github.com/pierrecomputer/pierre/tree/main/packages/trees)** for the directory tree's renderer-agnostic sort and path model (vendored into `src/vendor/pierre/`, not a runtime dependency).\n- **Arktype** for runtime validation of config, CLI options, and external boundaries.\n- **ansis** for ANSI escape code helpers.\n- **smol-toml** for TOML config parsing.\n- **Biome** for formatting and linting.\n\n## Credits\n\n`suiseki` is built around the idea that Pierre's renderer-agnostic parsing and tree logic, paired with Shiki's syntax and theme ecosystem, can produce a better terminal viewing experience for code.\n\n- [`@pierre/diffs`](https://github.com/pierrecomputer/pierre/tree/main/packages/diffs) and [`@pierre/trees`](https://github.com/pierrecomputer/pierre/tree/main/packages/trees) by [The Pierre Computer Company](https://github.com/pierrecomputer/pierre) ([diffs.com](https://diffs.com), [trees.software](https://trees.software)), Apache 2.0.\n- [Shiki](https://shiki.style) ([github.com/shikijs/shiki](https://github.com/shikijs/shiki)), MIT.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeldlfx%2Fsuiseki","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichaeldlfx%2Fsuiseki","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeldlfx%2Fsuiseki/lists"}