diff --git a/bin/pbpaste-img b/bin/pbpaste-img
new file mode 100755
index 0000000..ed7b51e
--- /dev/null
+++ b/bin/pbpaste-img
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+#
+# Save the current Windows clipboard image into WSL and copy its WSL path.
+
+set -Eeuo pipefail
+
+script_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
+script_dir="$(cd -- "$(dirname -- "$script_file")" && pwd -P)"
+out_root="${PBPASTE_IMG_DIR:-$HOME/.local/pbpaste-img}"
+script_path="$script_dir/pbpaste-img.ps1"
+bashlib_path="$script_dir/../zsh/bashlib.sh"
+
+if ! command -v powershell.exe >/dev/null 2>&1; then
+ echo "ERROR: powershell.exe is required for Windows clipboard access." >&2
+ exit 1
+fi
+
+if ! command -v wslpath >/dev/null 2>&1; then
+ echo "ERROR: wslpath is required to bridge WSL and Windows paths." >&2
+ exit 1
+fi
+
+mkdir -p "$out_root"
+
+script_path_windows="$(wslpath -w "$script_path")"
+out_root_windows="$(wslpath -w "$out_root")"
+
+if [[ -f "$bashlib_path" ]]; then
+ # shellcheck source=/dev/null
+ source "$bashlib_path"
+fi
+
+powershell_args=(
+ -NoLogo
+ -NoProfile
+ -ExecutionPolicy Bypass
+ -STA
+ -File "$script_path_windows"
+ -OutRootWindows "$out_root_windows"
+)
+
+if declare -F is_wsl >/dev/null 2>&1 && is_wsl >/dev/null; then
+ powershell_args+=(-OutRootWsl "$out_root")
+else
+ powershell_args+=(-WindowsPathToClipboard)
+fi
+
+powershell.exe "${powershell_args[@]}" "$@" | tr -d '\r'
diff --git a/bin/pbpaste-img.ps1 b/bin/pbpaste-img.ps1
new file mode 100644
index 0000000..9d44144
--- /dev/null
+++ b/bin/pbpaste-img.ps1
@@ -0,0 +1,97 @@
+[CmdletBinding()]
+param(
+ [string]$OutRootWindows,
+ [string]$OutRootWsl = $env:PBPASTE_IMG_ROOT_WSL,
+ [switch]$WindowsPathToClipboard
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+
+Add-Type -AssemblyName System.Windows.Forms
+Add-Type -AssemblyName System.Drawing
+
+function Invoke-WslText {
+ param([string[]]$Arguments)
+
+ $output = & wsl.exe @Arguments
+ if ($LASTEXITCODE -ne 0) {
+ throw "wsl.exe $($Arguments -join ' ') failed with exit code $LASTEXITCODE"
+ }
+
+ return (($output -join "`n").Trim())
+}
+
+function Invoke-WslChecked {
+ param([string[]]$Arguments)
+
+ & wsl.exe @Arguments | Out-Null
+ if ($LASTEXITCODE -ne 0) {
+ throw "wsl.exe $($Arguments -join ' ') failed with exit code $LASTEXITCODE"
+ }
+}
+
+function Join-WslPath {
+ param(
+ [string]$Root,
+ [string]$Child
+ )
+
+ return (($Root.TrimEnd("/", "\")) + "/" + $Child)
+}
+
+if ([string]::IsNullOrWhiteSpace($OutRootWsl) -and
+ [string]::IsNullOrWhiteSpace($OutRootWindows)) {
+ $wslHome = Invoke-WslText -Arguments @("sh", "-lc", 'printf %s "$HOME"')
+ $OutRootWsl = Join-WslPath -Root $wslHome -Child ".local/pbpaste-img"
+}
+
+$month = Get-Date -Format "yy-MM"
+$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
+$fileName = "$timestamp.png"
+
+if (-not [string]::IsNullOrWhiteSpace($OutRootWsl)) {
+ $outDirWsl = Join-WslPath -Root $OutRootWsl -Child $month
+ Invoke-WslChecked -Arguments @("mkdir", "-p", $outDirWsl)
+} else {
+ $outDirWsl = $null
+}
+
+if ([string]::IsNullOrWhiteSpace($OutRootWindows)) {
+ if ([string]::IsNullOrWhiteSpace($outDirWsl)) {
+ throw "Either -OutRootWindows or -OutRootWsl must be set."
+ }
+ $outDirWindows = Invoke-WslText -Arguments @("wslpath", "-w", $outDirWsl)
+} else {
+ $outDirWindows = Join-Path $OutRootWindows $month
+ New-Item -ItemType Directory -Force -Path $outDirWindows | Out-Null
+}
+
+$outFileWindows = Join-Path $outDirWindows $fileName
+
+if (-not [string]::IsNullOrWhiteSpace($outDirWsl)) {
+ $outFileWsl = Join-WslPath -Root $outDirWsl -Child $fileName
+} else {
+ $outFileWsl = $null
+}
+
+$image = [System.Windows.Forms.Clipboard]::GetImage()
+if ($null -eq $image) {
+ Write-Error "No bitmap image found on the clipboard."
+ exit 1
+}
+
+try {
+ $image.Save($outFileWindows, [System.Drawing.Imaging.ImageFormat]::Png)
+} finally {
+ $image.Dispose()
+}
+
+if ($WindowsPathToClipboard -or [string]::IsNullOrWhiteSpace($outFileWsl)) {
+ $clipboardPath = $outFileWindows
+} else {
+ $clipboardPath = $outFileWsl
+}
+
+Set-Clipboard -Value $clipboardPath
+Write-Output $clipboardPath
diff --git a/css/README.md b/css/README.md
new file mode 100644
index 0000000..1877f24
--- /dev/null
+++ b/css/README.md
@@ -0,0 +1,29 @@
+# CSS Assets
+
+## `github-markdown.min.css`
+
+Downloaded from Cloudflare cdnjs for local Pandoc/Markdown previews.
+
+- Package: `github-markdown-css`
+- Version: `5.8.1`
+- Source URL: `https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown.min.css`
+- Local path: `$DOTFILES/css/github-markdown.min.css`
+
+## `github-markdown-pandoc.css`
+
+Small local override for Pandoc-generated HTML. The upstream
+`github-markdown-css` package expects rendered Markdown to live inside
+`.markdown-body`, but it does not set the GitHub-like page width/padding by
+itself.
+
+- Local path: `$DOTFILES/css/github-markdown-pandoc.css`
+- Adds GitHub's typical `max-width: 980px`, centered layout, and `45px` page
+ padding for `.markdown-body`.
+
+Example usage:
+
+```bash
+GH_MD_CSS="$DOTFILES/css/github-markdown.min.css"
+GH_PANDOC_CSS="$DOTFILES/css/github-markdown-pandoc.css"
+auto-pandoc calc-liq.md -s --katex --css="$GH_MD_CSS" --css="$GH_PANDOC_CSS" -o index.html
+```
diff --git a/css/github-markdown-pandoc.css b/css/github-markdown-pandoc.css
new file mode 100644
index 0000000..26eae60
--- /dev/null
+++ b/css/github-markdown-pandoc.css
@@ -0,0 +1,19 @@
+.markdown-body {
+ box-sizing: border-box;
+ min-width: 200px;
+ max-width: 980px;
+ margin: 0 auto;
+ padding: 45px;
+}
+
+@media (max-width: 767px) {
+ .markdown-body {
+ padding: 15px;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #0d1117;
+ }
+}
diff --git a/css/github-markdown.min.css b/css/github-markdown.min.css
new file mode 100644
index 0000000..f9f1a28
--- /dev/null
+++ b/css/github-markdown.min.css
@@ -0,0 +1 @@
+.markdown-body{--base-size-4:0.25rem;--base-size-8:0.5rem;--base-size-16:1rem;--base-size-24:1.5rem;--base-size-40:2.5rem;--base-text-weight-normal:400;--base-text-weight-medium:500;--base-text-weight-semibold:600;--fontStack-monospace:ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;--fgColor-accent:Highlight}@media (prefers-color-scheme:dark){.markdown-body,[data-theme=dark]{color-scheme:dark;--focus-outlineColor:#1f6feb;--fgColor-default:#f0f6fc;--fgColor-muted:#9198a1;--fgColor-accent:#4493f8;--fgColor-success:#3fb950;--fgColor-attention:#d29922;--fgColor-danger:#f85149;--fgColor-done:#ab7df8;--bgColor-default:#0d1117;--bgColor-muted:#151b23;--bgColor-neutral-muted:#656c7633;--bgColor-attention-muted:#bb800926;--borderColor-default:#3d444d;--borderColor-muted:#3d444db3;--borderColor-neutral-muted:#3d444db3;--borderColor-accent-emphasis:#1f6feb;--borderColor-success-emphasis:#238636;--borderColor-attention-emphasis:#9e6a03;--borderColor-danger-emphasis:#da3633;--borderColor-done-emphasis:#8957e5;--color-prettylights-syntax-comment:#9198a1;--color-prettylights-syntax-constant:#79c0ff;--color-prettylights-syntax-constant-other-reference-link:#a5d6ff;--color-prettylights-syntax-entity:#d2a8ff;--color-prettylights-syntax-storage-modifier-import:#f0f6fc;--color-prettylights-syntax-entity-tag:#7ee787;--color-prettylights-syntax-keyword:#ff7b72;--color-prettylights-syntax-string:#a5d6ff;--color-prettylights-syntax-variable:#ffa657;--color-prettylights-syntax-brackethighlighter-unmatched:#f85149;--color-prettylights-syntax-brackethighlighter-angle:#9198a1;--color-prettylights-syntax-invalid-illegal-text:#f0f6fc;--color-prettylights-syntax-invalid-illegal-bg:#8e1519;--color-prettylights-syntax-carriage-return-text:#f0f6fc;--color-prettylights-syntax-carriage-return-bg:#b62324;--color-prettylights-syntax-string-regexp:#7ee787;--color-prettylights-syntax-markup-list:#f2cc60;--color-prettylights-syntax-markup-heading:#1f6feb;--color-prettylights-syntax-markup-italic:#f0f6fc;--color-prettylights-syntax-markup-bold:#f0f6fc;--color-prettylights-syntax-markup-deleted-text:#ffdcd7;--color-prettylights-syntax-markup-deleted-bg:#67060c;--color-prettylights-syntax-markup-inserted-text:#aff5b4;--color-prettylights-syntax-markup-inserted-bg:#033a16;--color-prettylights-syntax-markup-changed-text:#ffdfb6;--color-prettylights-syntax-markup-changed-bg:#5a1e02;--color-prettylights-syntax-markup-ignored-text:#f0f6fc;--color-prettylights-syntax-markup-ignored-bg:#1158c7;--color-prettylights-syntax-meta-diff-range:#d2a8ff;--color-prettylights-syntax-sublimelinter-gutter-mark:#3d444d}}@media (prefers-color-scheme:light){.markdown-body,[data-theme=light]{color-scheme:light;--focus-outlineColor:#0969da;--fgColor-default:#1f2328;--fgColor-muted:#59636e;--fgColor-accent:#0969da;--fgColor-success:#1a7f37;--fgColor-attention:#9a6700;--fgColor-danger:#d1242f;--fgColor-done:#8250df;--bgColor-default:#ffffff;--bgColor-muted:#f6f8fa;--bgColor-neutral-muted:#818b981f;--bgColor-attention-muted:#fff8c5;--borderColor-default:#d1d9e0;--borderColor-muted:#d1d9e0b3;--borderColor-neutral-muted:#d1d9e0b3;--borderColor-accent-emphasis:#0969da;--borderColor-success-emphasis:#1a7f37;--borderColor-attention-emphasis:#9a6700;--borderColor-danger-emphasis:#cf222e;--borderColor-done-emphasis:#8250df;--color-prettylights-syntax-comment:#59636e;--color-prettylights-syntax-constant:#0550ae;--color-prettylights-syntax-constant-other-reference-link:#0a3069;--color-prettylights-syntax-entity:#6639ba;--color-prettylights-syntax-storage-modifier-import:#1f2328;--color-prettylights-syntax-entity-tag:#0550ae;--color-prettylights-syntax-keyword:#cf222e;--color-prettylights-syntax-string:#0a3069;--color-prettylights-syntax-variable:#953800;--color-prettylights-syntax-brackethighlighter-unmatched:#82071e;--color-prettylights-syntax-brackethighlighter-angle:#59636e;--color-prettylights-syntax-invalid-illegal-text:#f6f8fa;--color-prettylights-syntax-invalid-illegal-bg:#82071e;--color-prettylights-syntax-carriage-return-text:#f6f8fa;--color-prettylights-syntax-carriage-return-bg:#cf222e;--color-prettylights-syntax-string-regexp:#116329;--color-prettylights-syntax-markup-list:#3b2300;--color-prettylights-syntax-markup-heading:#0550ae;--color-prettylights-syntax-markup-italic:#1f2328;--color-prettylights-syntax-markup-bold:#1f2328;--color-prettylights-syntax-markup-deleted-text:#82071e;--color-prettylights-syntax-markup-deleted-bg:#ffebe9;--color-prettylights-syntax-markup-inserted-text:#116329;--color-prettylights-syntax-markup-inserted-bg:#dafbe1;--color-prettylights-syntax-markup-changed-text:#953800;--color-prettylights-syntax-markup-changed-bg:#ffd8b5;--color-prettylights-syntax-markup-ignored-text:#d1d9e0;--color-prettylights-syntax-markup-ignored-bg:#0550ae;--color-prettylights-syntax-meta-diff-range:#8250df;--color-prettylights-syntax-sublimelinter-gutter-mark:#818b98}}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;margin:0;color:var(--fgColor-default);background-color:var(--bgColor-default);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body .octicon{display:inline-block;fill:currentColor;vertical-align:text-bottom}.markdown-body h1:hover .anchor .octicon-link:before,.markdown-body h2:hover .anchor .octicon-link:before,.markdown-body h3:hover .anchor .octicon-link:before,.markdown-body h4:hover .anchor .octicon-link:before,.markdown-body h5:hover .anchor .octicon-link:before,.markdown-body h6:hover .anchor .octicon-link:before{width:16px;height:16px;content:' ';display:inline-block;background-color:currentColor;-webkit-mask-image:url("data:image/svg+xml,");mask-image:url("data:image/svg+xml,")}.markdown-body details,.markdown-body figcaption,.markdown-body figure{display:block}.markdown-body summary{display:list-item}.markdown-body [hidden]{display:none!important}.markdown-body a{background-color:transparent;color:var(--fgColor-accent);text-decoration:none}.markdown-body abbr[title]{border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.markdown-body b,.markdown-body strong{font-weight:var(--base-text-weight-semibold,600)}.markdown-body dfn{font-style:italic}.markdown-body h1{margin:.67em 0;font-weight:var(--base-text-weight-semibold,600);padding-bottom:.3em;font-size:2em;border-bottom:1px solid var(--borderColor-muted)}.markdown-body mark{background-color:var(--bgColor-attention-muted);color:var(--fgColor-default)}.markdown-body small{font-size:90%}.markdown-body sub,.markdown-body sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.markdown-body sub{bottom:-.25em}.markdown-body sup{top:-.5em}.markdown-body img{border-style:none;max-width:100%;box-sizing:content-box}.markdown-body code,.markdown-body kbd,.markdown-body pre,.markdown-body samp{font-family:monospace;font-size:1em}.markdown-body figure{margin:1em var(--base-size-40)}.markdown-body hr{box-sizing:content-box;overflow:hidden;background:0 0;border-bottom:1px solid var(--borderColor-muted);height:.25em;padding:0;margin:var(--base-size-24) 0;background-color:var(--borderColor-default);border:0}.markdown-body input{font:inherit;margin:0;overflow:visible;font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body [type=button],.markdown-body [type=reset],.markdown-body [type=submit]{-webkit-appearance:button;appearance:button}.markdown-body [type=checkbox],.markdown-body [type=radio]{box-sizing:border-box;padding:0}.markdown-body [type=number]::-webkit-inner-spin-button,.markdown-body [type=number]::-webkit-outer-spin-button{height:auto}.markdown-body [type=search]::-webkit-search-cancel-button,.markdown-body [type=search]::-webkit-search-decoration{-webkit-appearance:none;appearance:none}.markdown-body ::-webkit-input-placeholder{color:inherit;opacity:.54}.markdown-body ::-webkit-file-upload-button{-webkit-appearance:button;appearance:button;font:inherit}.markdown-body a:hover{text-decoration:underline}.markdown-body ::placeholder{color:var(--fgColor-muted);opacity:1}.markdown-body hr::before{display:table;content:""}.markdown-body hr::after{display:table;clear:both;content:""}.markdown-body table{border-spacing:0;border-collapse:collapse;display:block;width:max-content;max-width:100%;overflow:auto;font-variant:tabular-nums}.markdown-body td,.markdown-body th{padding:0}.markdown-body details summary{cursor:pointer}.markdown-body [role=button]:focus,.markdown-body a:focus,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=radio]:focus{outline:2px solid var(--focus-outlineColor);outline-offset:-2px;box-shadow:none}.markdown-body [role=button]:focus:not(:focus-visible),.markdown-body a:focus:not(:focus-visible),.markdown-body input[type=checkbox]:focus:not(:focus-visible),.markdown-body input[type=radio]:focus:not(:focus-visible){outline:solid 1px transparent}.markdown-body [role=button]:focus-visible,.markdown-body a:focus-visible,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus-visible{outline:2px solid var(--focus-outlineColor);outline-offset:-2px;box-shadow:none}.markdown-body a:not([class]):focus,.markdown-body a:not([class]):focus-visible,.markdown-body input[type=checkbox]:focus,.markdown-body input[type=checkbox]:focus-visible,.markdown-body input[type=radio]:focus,.markdown-body input[type=radio]:focus-visible{outline-offset:0}.markdown-body kbd{display:inline-block;padding:var(--base-size-4);font:11px var(--fontStack-monospace,ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace);line-height:10px;color:var(--fgColor-default);vertical-align:middle;background-color:var(--bgColor-muted);border:solid 1px var(--borderColor-neutral-muted);border-bottom-color:var(--borderColor-neutral-muted);border-radius:6px;box-shadow:inset 0 -1px 0 var(--borderColor-neutral-muted)}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:var(--base-size-24);margin-bottom:var(--base-size-16);font-weight:var(--base-text-weight-semibold,600);line-height:1.25}.markdown-body h2{font-weight:var(--base-text-weight-semibold,600);padding-bottom:.3em;font-size:1.5em;border-bottom:1px solid var(--borderColor-muted)}.markdown-body h3{font-weight:var(--base-text-weight-semibold,600);font-size:1.25em}.markdown-body h4{font-weight:var(--base-text-weight-semibold,600);font-size:1em}.markdown-body h5{font-weight:var(--base-text-weight-semibold,600);font-size:.875em}.markdown-body h6{font-weight:var(--base-text-weight-semibold,600);font-size:.85em;color:var(--fgColor-muted)}.markdown-body p{margin-top:0;margin-bottom:10px}.markdown-body blockquote{margin:0;padding:0 1em;color:var(--fgColor-muted);border-left:.25em solid var(--borderColor-default)}.markdown-body ol,.markdown-body ul{margin-top:0;margin-bottom:0;padding-left:2em}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code,.markdown-body samp,.markdown-body tt{font-family:var(--fontStack-monospace,ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace);font-size:12px}.markdown-body pre{margin-top:0;margin-bottom:0;font-family:var(--fontStack-monospace,ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace);font-size:12px;word-wrap:normal}.markdown-body .octicon{display:inline-block;overflow:visible!important;vertical-align:text-bottom;fill:currentColor}.markdown-body input::-webkit-inner-spin-button,.markdown-body input::-webkit-outer-spin-button{margin:0;appearance:none}.markdown-body .mr-2{margin-right:var(--base-size-8,8px)!important}.markdown-body::before{display:table;content:""}.markdown-body::after{display:table;clear:both;content:""}.markdown-body>:first-child{margin-top:0!important}.markdown-body>:last-child{margin-bottom:0!important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:var(--fgColor-danger)}.markdown-body .anchor{float:left;padding-right:var(--base-size-4);margin-left:-20px;line-height:1}.markdown-body .anchor:focus{outline:0}.markdown-body blockquote,.markdown-body details,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-top:0;margin-bottom:var(--base-size-16)}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:var(--fgColor-default);vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 code,.markdown-body h1 tt,.markdown-body h2 code,.markdown-body h2 tt,.markdown-body h3 code,.markdown-body h3 tt,.markdown-body h4 code,.markdown-body h4 tt,.markdown-body h5 code,.markdown-body h5 tt,.markdown-body h6 code,.markdown-body h6 tt{padding:0 .2em;font-size:inherit}.markdown-body summary h1,.markdown-body summary h2,.markdown-body summary h3,.markdown-body summary h4,.markdown-body summary h5,.markdown-body summary h6{display:inline-block}.markdown-body summary h1 .anchor,.markdown-body summary h2 .anchor,.markdown-body summary h3 .anchor,.markdown-body summary h4 .anchor,.markdown-body summary h5 .anchor,.markdown-body summary h6 .anchor{margin-left:-40px}.markdown-body summary h1,.markdown-body summary h2{padding-bottom:0;border-bottom:0}.markdown-body ol.no-list,.markdown-body ul.no-list{padding:0;list-style-type:none}.markdown-body ol[type="a s"]{list-style-type:lower-alpha}.markdown-body ol[type="A s"]{list-style-type:upper-alpha}.markdown-body ol[type="i s"]{list-style-type:lower-roman}.markdown-body ol[type="I s"]{list-style-type:upper-roman}.markdown-body ol[type="1"]{list-style-type:decimal}.markdown-body div>ol:not([type]){list-style-type:decimal}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:var(--base-size-16)}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:var(--base-size-16);font-size:1em;font-style:italic;font-weight:var(--base-text-weight-semibold,600)}.markdown-body dl dd{padding:0 var(--base-size-16);margin-bottom:var(--base-size-16)}.markdown-body table th{font-weight:var(--base-text-weight-semibold,600)}.markdown-body table td,.markdown-body table th{padding:6px 13px;border:1px solid var(--borderColor-default)}.markdown-body table td>:last-child{margin-bottom:0}.markdown-body table tr{background-color:var(--bgColor-default);border-top:1px solid var(--borderColor-muted)}.markdown-body table tr:nth-child(2n){background-color:var(--bgColor-muted)}.markdown-body table img{background-color:transparent}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:transparent}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid var(--borderColor-default)}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:var(--fgColor-default)}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:.2em .4em;margin:0;font-size:85%;white-space:break-spaces;background-color:var(--bgColor-neutral-muted);border-radius:6px}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body samp{font-size:85%}.markdown-body pre code{font-size:100%}.markdown-body pre>code{padding:0;margin:0;word-break:normal;white-space:pre;background:0 0;border:0}.markdown-body .highlight{margin-bottom:var(--base-size-16)}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:var(--base-size-16);overflow:auto;font-size:85%;line-height:1.45;color:var(--fgColor-default);background-color:var(--bgColor-muted);border-radius:6px}.markdown-body pre code,.markdown-body pre tt{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px var(--base-size-8) 9px;text-align:right;background:var(--bgColor-default);border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:var(--base-text-weight-semibold,600);background:var(--bgColor-muted);border-top:0}.markdown-body [data-footnote-ref]::before{content:"["}.markdown-body [data-footnote-ref]::after{content:"]"}.markdown-body .footnotes{font-size:12px;color:var(--fgColor-muted);border-top:1px solid var(--borderColor-default)}.markdown-body .footnotes ol{padding-left:var(--base-size-16)}.markdown-body .footnotes ol ul{display:inline-block;padding-left:var(--base-size-16);margin-top:var(--base-size-16)}.markdown-body .footnotes li{position:relative}.markdown-body .footnotes li:target::before{position:absolute;top:calc(var(--base-size-8)*-1);right:calc(var(--base-size-8)*-1);bottom:calc(var(--base-size-8)*-1);left:calc(var(--base-size-24)*-1);pointer-events:none;content:"";border:2px solid var(--borderColor-accent-emphasis);border-radius:6px}.markdown-body .footnotes li:target{color:var(--fgColor-default)}.markdown-body .footnotes .data-footnote-backref g-emoji{font-family:monospace}.markdown-body body:has(:modal){padding-right:var(--dialog-scrollgutter)!important}.markdown-body .pl-c{color:var(--color-prettylights-syntax-comment)}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:var(--color-prettylights-syntax-constant)}.markdown-body .pl-e,.markdown-body .pl-en{color:var(--color-prettylights-syntax-entity)}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:var(--color-prettylights-syntax-storage-modifier-import)}.markdown-body .pl-ent{color:var(--color-prettylights-syntax-entity-tag)}.markdown-body .pl-k{color:var(--color-prettylights-syntax-keyword)}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:var(--color-prettylights-syntax-string)}.markdown-body .pl-smw,.markdown-body .pl-v{color:var(--color-prettylights-syntax-variable)}.markdown-body .pl-bu{color:var(--color-prettylights-syntax-brackethighlighter-unmatched)}.markdown-body .pl-ii{color:var(--color-prettylights-syntax-invalid-illegal-text);background-color:var(--color-prettylights-syntax-invalid-illegal-bg)}.markdown-body .pl-c2{color:var(--color-prettylights-syntax-carriage-return-text);background-color:var(--color-prettylights-syntax-carriage-return-bg)}.markdown-body .pl-sr .pl-cce{font-weight:700;color:var(--color-prettylights-syntax-string-regexp)}.markdown-body .pl-ml{color:var(--color-prettylights-syntax-markup-list)}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{font-weight:700;color:var(--color-prettylights-syntax-markup-heading)}.markdown-body .pl-mi{font-style:italic;color:var(--color-prettylights-syntax-markup-italic)}.markdown-body .pl-mb{font-weight:700;color:var(--color-prettylights-syntax-markup-bold)}.markdown-body .pl-md{color:var(--color-prettylights-syntax-markup-deleted-text);background-color:var(--color-prettylights-syntax-markup-deleted-bg)}.markdown-body .pl-mi1{color:var(--color-prettylights-syntax-markup-inserted-text);background-color:var(--color-prettylights-syntax-markup-inserted-bg)}.markdown-body .pl-mc{color:var(--color-prettylights-syntax-markup-changed-text);background-color:var(--color-prettylights-syntax-markup-changed-bg)}.markdown-body .pl-mi2{color:var(--color-prettylights-syntax-markup-ignored-text);background-color:var(--color-prettylights-syntax-markup-ignored-bg)}.markdown-body .pl-mdr{font-weight:700;color:var(--color-prettylights-syntax-meta-diff-range)}.markdown-body .pl-ba{color:var(--color-prettylights-syntax-brackethighlighter-angle)}.markdown-body .pl-sg{color:var(--color-prettylights-syntax-sublimelinter-gutter-mark)}.markdown-body .pl-corl{text-decoration:underline;color:var(--color-prettylights-syntax-constant-other-reference-link)}.markdown-body [role=button]:focus:not(:focus-visible),.markdown-body [role=tabpanel][tabindex="0"]:focus:not(:focus-visible),.markdown-body a:focus:not(:focus-visible),.markdown-body button:focus:not(:focus-visible),.markdown-body summary:focus:not(:focus-visible){outline:0;box-shadow:none}.markdown-body [tabindex="0"]:focus:not(:focus-visible),.markdown-body details-dialog:focus:not(:focus-visible){outline:0}.markdown-body g-emoji{display:inline-block;min-width:1ch;font-family:"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1em;font-style:normal!important;font-weight:var(--base-text-weight-normal,400);line-height:1;vertical-align:-.075em}.markdown-body g-emoji img{width:1em;height:1em}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item label{font-weight:var(--base-text-weight-normal,400)}.markdown-body .task-list-item.enabled label{cursor:pointer}.markdown-body .task-list-item+.task-list-item{margin-top:var(--base-size-4)}.markdown-body .task-list-item .handle{display:none}.markdown-body .task-list-item-checkbox{margin:0 .2em .25em -1.4em;vertical-align:middle}.markdown-body ul:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}.markdown-body ol:dir(rtl) .task-list-item-checkbox{margin:0 -1.6em .25em .2em}.markdown-body .contains-task-list:focus-within .task-list-item-convert-container,.markdown-body .contains-task-list:hover .task-list-item-convert-container{display:block;width:auto;height:24px;overflow:visible;clip:auto}.markdown-body ::-webkit-calendar-picker-indicator{filter:invert(50%)}.markdown-body .markdown-alert{padding:var(--base-size-8) var(--base-size-16);margin-bottom:var(--base-size-16);color:inherit;border-left:.25em solid var(--borderColor-default)}.markdown-body .markdown-alert>:first-child{margin-top:0}.markdown-body .markdown-alert>:last-child{margin-bottom:0}.markdown-body .markdown-alert .markdown-alert-title{display:flex;font-weight:var(--base-text-weight-medium,500);align-items:center;line-height:1}.markdown-body .markdown-alert.markdown-alert-note{border-left-color:var(--borderColor-accent-emphasis)}.markdown-body .markdown-alert.markdown-alert-note .markdown-alert-title{color:var(--fgColor-accent)}.markdown-body .markdown-alert.markdown-alert-important{border-left-color:var(--borderColor-done-emphasis)}.markdown-body .markdown-alert.markdown-alert-important .markdown-alert-title{color:var(--fgColor-done)}.markdown-body .markdown-alert.markdown-alert-warning{border-left-color:var(--borderColor-attention-emphasis)}.markdown-body .markdown-alert.markdown-alert-warning .markdown-alert-title{color:var(--fgColor-attention)}.markdown-body .markdown-alert.markdown-alert-tip{border-left-color:var(--borderColor-success-emphasis)}.markdown-body .markdown-alert.markdown-alert-tip .markdown-alert-title{color:var(--fgColor-success)}.markdown-body .markdown-alert.markdown-alert-caution{border-left-color:var(--borderColor-danger-emphasis)}.markdown-body .markdown-alert.markdown-alert-caution .markdown-alert-title{color:var(--fgColor-danger)}.markdown-body>:first-child>.heading-element:first-child{margin-top:0!important}.markdown-body .highlight pre:has(+.zeroclipboard-container){min-height:52px}
\ No newline at end of file
diff --git a/justfile b/justfile
index c1c8977..f5da960 100644
--- a/justfile
+++ b/justfile
@@ -14,7 +14,8 @@ alias t := test
# Install baseline Ubuntu/WSL shell dependencies.
i-bash:
- sudo apt install -y build-essential ripgrep gh libclang-dev wslu
+ sudo apt install -y build-essential ripgrep gh libclang-dev wslu \
+ ca-certificates gnupg curl trash-cli
# Install shell dependencies needed by CI tests.
i-bash-ci:
diff --git a/skillsSync.test.ts b/skillsSync.test.ts
index 305b8fa..f94ede5 100644
--- a/skillsSync.test.ts
+++ b/skillsSync.test.ts
@@ -19,7 +19,7 @@ const makeSkill = async (
const runSkillsSync = async (
homeDir: string,
- bokuPath: string,
+ repoPath: string,
args: string[] = ["--run"],
): Promise => {
const proc = Bun.spawn(["bun", scriptPath, ...args], {
@@ -27,7 +27,7 @@ const runSkillsSync = async (
env: {
...process.env,
HOME: homeDir,
- BOKU_PATH: bokuPath,
+ REPO: repoPath,
},
stderr: "pipe",
stdout: "pipe",
@@ -57,11 +57,11 @@ describe("skills-sync", () => {
let testCfg: {
root: string
syncHomeDir: string
- syncBokuPath: string
+ syncRepoPath: string
syncPublicDir: string
syncPrivateDir: string
dryRunHomeDir: string
- dryRunBokuPath: string
+ dryRunRepoPath: string
dryRunPublicDir: string
}
@@ -69,7 +69,8 @@ describe("skills-sync", () => {
const root = await mkdtemp(join(tmpdir(), "skills-sync-test-"))
const syncHomeDir = join(root, "sync-home")
- const syncBokuPath = join(root, "sync-boku")
+ const syncRepoPath = join(root, "sync-repo")
+ const syncBokuPath = join(syncRepoPath, "boku")
const syncRuntimeDir = join(syncHomeDir, ".cursor/skills")
const syncPublicDir = join(syncBokuPath, "jiyuu/ai-skills")
const syncPrivateDir = join(syncBokuPath, "priv-skills")
@@ -111,7 +112,8 @@ describe("skills-sync", () => {
)
const dryRunHomeDir = join(root, "dry-run-home")
- const dryRunBokuPath = join(root, "dry-run-boku")
+ const dryRunRepoPath = join(root, "dry-run-repo")
+ const dryRunBokuPath = join(dryRunRepoPath, "boku")
const dryRunRuntimeDir = join(dryRunHomeDir, ".cursor/skills")
const dryRunPublicDir = join(dryRunBokuPath, "jiyuu/ai-skills")
@@ -122,23 +124,23 @@ describe("skills-sync", () => {
testCfg = {
root,
syncHomeDir,
- syncBokuPath,
+ syncRepoPath,
syncPublicDir,
syncPrivateDir,
dryRunHomeDir,
- dryRunBokuPath,
+ dryRunRepoPath,
dryRunPublicDir,
}
})
test("defaults to dry run unless --run is passed", async () => {
- await runSkillsSync(testCfg.dryRunHomeDir, testCfg.dryRunBokuPath, [])
+ await runSkillsSync(testCfg.dryRunHomeDir, testCfg.dryRunRepoPath, [])
expect(await dirNames(testCfg.dryRunPublicDir)).toEqual(["stale-public"])
})
test("syncs direct runtime skills to public and private repos", async () => {
- await runSkillsSync(testCfg.syncHomeDir, testCfg.syncBokuPath)
+ await runSkillsSync(testCfg.syncHomeDir, testCfg.syncRepoPath)
expect(await dirNames(testCfg.syncPublicDir)).toEqual([
"public-false",
diff --git a/skillsSync.ts b/skillsSync.ts
index e3f70fe..6b7374a 100644
--- a/skillsSync.ts
+++ b/skillsSync.ts
@@ -40,16 +40,18 @@ if (help) {
const defaultConfig = (env: NodeJS.ProcessEnv): SkillsSyncConfig => {
const home = env.HOME
- const bokuPath = env.BOKU_PATH
+ const repo = env.REPO
if (!home) {
throw new Error("HOME is not set")
}
- if (!bokuPath) {
- throw new Error("BOKU_PATH is not set")
+ if (!repo) {
+ throw new Error("REPO is not set")
}
+ const bokuPath = resolve(repo, "boku")
+
return {
skillsRuntime: resolve(home, ".cursor/skills"),
publicSkillsDir: resolve(bokuPath, "jiyuu/ai-skills"),
diff --git a/zsh/aliases.sh b/zsh/aliases.sh
index 5f85a49..92a6053 100644
--- a/zsh/aliases.sh
+++ b/zsh/aliases.sh
@@ -2,28 +2,27 @@
alias ohmyzsh="nvim ~/.oh-my-zsh"
-# MongoDB alias commands
+# MongoDB alias commands
alias mongo="/mnt/c/Program\ Files/MongoDB/Server/5.0/bin/mongo.exe"
alias mongod="/mnt/c/Program\ Files/MongoDB/Server/5.0/bin/mongod.exe"
alias mongosh="/mnt/c/Users/realu/AppData/Local/Programs/mongosh/mongosh.exe"
alias mongos="/mnt/c/Program\ Files/MongoDB/Server/5.0/bin/mongos.exe"
# Unique Divine linux commands
-alias ls="exa" # cargo install exa
-alias la='exa -a'
-alias s='sudo'
-alias yarn-offline='yarn --prefer-offline'
-alias cat="bat" # cargo install bat
+alias ls="exa" # cargo install exa
+alias la="exa -a"
+alias s="sudo"
+alias yarn-offline="yarn --prefer-offline"
+alias cat="bat" # cargo install bat
# NOTE: This is important - sudo apt install build-essential
+alias rm="trash -v"
alias vi="nvim"
alias vim="nvim"
-alias rt="ud rs test-short"
-
# vv: short for "vim view". Piping to vv lets you read stdout in a vi editor
alias vv="view -"
-alias vvjq="jq | vv"
+alias vvjq="jq | view -"
alias ess="2>&1"
@@ -34,7 +33,7 @@ git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
-git config --global alias.ac '!git add -A && git commit'
+git config --global alias.ac "!git add -A && git commit"
git config --global core.editor "nvim"
# Broadcast tx and open it in vim.
diff --git a/zsh/quick.sh b/zsh/quick.sh
index 375bb1a..deb9d74 100644
--- a/zsh/quick.sh
+++ b/zsh/quick.sh
@@ -4,7 +4,7 @@
# ℹ️ -------- IMPORTS -------- ℹ️
source "$DOTFILES/zsh/bashlib.sh"
-# The $DOTFILES and $BOKU_PATH variables are exported from .zshenv.
+# The $REPO and $DOTFILES variables are exported from .zshenv.
# ----------------- Git -----------------
# Some of my most-used commands. I rely on these daily to quickly finish
@@ -73,19 +73,29 @@ git_cipb() {
# your text-based TODO-list open.
todos() {
local before="$(pwd)"
- cd "$BOKU_PATH" || return 1
- nvim "$BOKU_PATH/free/todos.md"
+ cd "$REPO/boku" || return 1
+ nvim "$REPO/boku/free/todos.md"
cd "$before" || return 1
}
# notes: Opens NeoVim with a notes workspace as the working directory.
notes() {
local before="$(pwd)"
- cd "$BOKU_PATH" || return 1
- nvim "$BOKU_PATH/free/the-log.md"
+ cd "$REPO/boku" || return 1
+ nvim "$REPO/boku/free/the-log.md"
cd "$before" || return 1
}
+# todos: Opens NeoVim with your notes workspace as the working directory with
+# your text-based TODO-list open.
+epics() {
+ (
+ # subshell retains path from prior to the edit
+ cd "$REPO/boku"
+ nvim "$REPO/boku/epics"
+ )
+}
+
# music: Opens the Windows file explorer to your music files.
music() {
local before="$(pwd)"
@@ -129,7 +139,7 @@ skills() {
cd "$skills_runtime"
nvim "$skills_runtime"
- cd "$BOKU_PATH/dotfiles"
+ cd "$REPO/boku/dotfiles"
just skills-sync --run
cd "$before"
@@ -156,7 +166,7 @@ cfg_tmux() {
# sharex: ShareX screenshots CLI. Pass --run (nvim) or --explorer; no args -> help.
sharex() {
- bash "$BOKU_PATH/sharex.sh" "$@"
+ bash "$REPO/boku/sharex.sh" "$@"
}
# ----------------- Nibiru -----------------
diff --git a/zsh/zshenv b/zsh/zshenv
index 199a40b..bd25b65 100644
--- a/zsh/zshenv
+++ b/zsh/zshenv
@@ -1,7 +1,7 @@
. "$HOME/.cargo/env"
-export BOKU_PATH="$HOME/ki/boku"
-export DOTFILES="$BOKU_PATH/dotfiles"
+export REPO="$HOME/ki"
+export DOTFILES="$REPO/boku/dotfiles"
source "$DOTFILES/symlinks.sh"
export OUTTXT="$HOME/ki/out.txt"
export SKILLS="$HOME/.cursor/skills"
-export EPICS="$BOKU_PATH/epics"
+export EPICS="$REPO/boku/epics"
diff --git a/zsh/zshrc b/zsh/zshrc
index c5595b0..0eb7662 100644
--- a/zsh/zshrc
+++ b/zsh/zshrc
@@ -3,7 +3,7 @@
# Contact: Unique Divine
# The .zshrc runs after .zshenv
-# The $DOTFILES and $BOKU_PATH variables are exported from .zshenv.
+# The $REPO and $DOTFILES variables are exported from .zshenv.
source $DOTFILES/zsh/bashlib.sh
main_bash_setup
@@ -351,3 +351,7 @@ sync_cursor_cli_config
source ~/powerlevel10k/powerlevel10k.zsh-theme
[[ -s "$HOME/.gvm/scripts/gvm" ]] && source "$HOME/.gvm/scripts/gvm"
+
+# >>> Codex installer >>>
+export PATH="$HOME/.local/bin:$PATH"
+# <<< Codex installer <<<