wip: pre-split snapshot

This commit is contained in:
Prad Nukala committed 2026-10-14 13:31:37 -04:00
1 parent b4f74342ff
commit ab29365a3d
21 files changed
+321 -65

No files matched your search

+9 -6
View File
@@ -65,6 +65,7 @@ Omarchy's default bindings stay enabled. `omarchy_default_bindings` and `omarchy
| `^com\.fastmail\.Fastmail$` | `special:fastmail silent` | | `^com\.fastmail\.Fastmail$` | `special:fastmail silent` |
| `^Beeper$` | `special:beeper silent` | | `^Beeper$` | `special:beeper silent` |
| `^brave-home\.google\.com__.*` | `special:home silent` | | `^brave-home\.google\.com__.*` | `special:home silent` |
| `^ai\.elementlabs\.lmstudio$` | `special:lmstudio silent` |
| `brave-(www\.)?youtube\.com__.*`, title not matching `.*Picture.?in.?[Pp]icture.*` | Float, pin, size 30% × 30% of the monitor, move to `10, monitor_h*0.7-10` (bottom-left corner) | | `brave-(www\.)?youtube\.com__.*`, title not matching `.*Picture.?in.?[Pp]icture.*` | Float, pin, size 30% × 30% of the monitor, move to `10, monitor_h*0.7-10` (bottom-left corner) |
Scratch residents are placed `silent` so that [`omarchy-toggle-scratch`](#omarchy-toggle-scratch) decides when to reveal them. The YouTube rule matches both `youtube.com` and `www.youtube.com` because Chromium's class changes once a video plays. It excludes the picture-in-picture overlay so Omarchy's own PiP rule still applies. Its size and inset must stay in sync with [`omarchy-pip`](#omarchy-pip). Scratch residents are placed `silent` so that [`omarchy-toggle-scratch`](#omarchy-toggle-scratch) decides when to reveal them. The YouTube rule matches both `youtube.com` and `www.youtube.com` because Chromium's class changes once a video plays. It excludes the picture-in-picture overlay so Omarchy's own PiP rule still applies. Its size and inset must stay in sync with [`omarchy-pip`](#omarchy-pip).
@@ -197,10 +198,10 @@ Named workspaces replace niri-dynamic-workspaces. `SUPER + ALT + S` (send to scr
| --- | --- | | --- | --- |
| `SUPER + R` | `omarchy-cycle-window-size` — next preset size | | `SUPER + R` | `omarchy-cycle-window-size` — next preset size |
| `SUPER + SHIFT + R` | `omarchy-cycle-window-size prev` | | `SUPER + SHIFT + R` | `omarchy-cycle-window-size prev` |
| `ALT + H` | Width −100 px | | `ALT + SHIFT + H` / `SUPER + ,` | Width −100 px |
| `ALT + L` | Width +100 px | | `ALT + SHIFT + L` / `SUPER + .` | Width +100 px |
| `ALT + K` | Height −100 px | | `SUPER + CTRL + K` | Height −100 px |
| `ALT + J` | Height +100 px | | `SUPER + CTRL + J` | Height +100 px |
| `SUPER + ALT + C` | Fit active column (scrolling layout only) | | `SUPER + ALT + C` | Fit active column (scrolling layout only) |
| `SUPER + SHIFT + ALT + C` | Fit visible columns (scrolling layout only) | | `SUPER + SHIFT + ALT + C` | Fit visible columns (scrolling layout only) |
@@ -218,6 +219,7 @@ Named workspaces replace niri-dynamic-workspaces. `SUPER + ALT + S` (send to scr
| `SUPER + CTRL + ALT + F` | Nautilus: `omarchy-launch-or-focus nautilus "gtk-launch org.gnome.Nautilus"` | — | | `SUPER + CTRL + ALT + F` | Nautilus: `omarchy-launch-or-focus nautilus "gtk-launch org.gnome.Nautilus"` | — |
| `SUPER + CTRL + ALT + E` | `omarchy-toggle-scratch fastmail` | — | | `SUPER + CTRL + ALT + E` | `omarchy-toggle-scratch fastmail` | — |
| `SUPER + CTRL + ALT + M` | `omarchy-toggle-scratch beeper` | — | | `SUPER + CTRL + ALT + M` | `omarchy-toggle-scratch beeper` | — |
| `SUPER + CTRL + L` | `omarchy-toggle-scratch lmstudio` (LM Studio) | toggle window gaps |
| `SUPER + CTRL + ALT + Y` | `omarchy-summon youtube` | web apps cycle | | `SUPER + CTRL + ALT + Y` | `omarchy-summon youtube` | web apps cycle |
| `SUPER + SHIFT + Y` | `omarchy-summon youtube` | YouTube desktop entry | | `SUPER + SHIFT + Y` | `omarchy-summon youtube` | YouTube desktop entry |
| `SUPER + CTRL + ALT + T` | tldraw offline: `omarchy-launch-or-focus tldraw-offline "gtk-launch tldraw-offline"` | show time | | `SUPER + CTRL + ALT + T` | tldraw offline: `omarchy-launch-or-focus tldraw-offline "gtk-launch tldraw-offline"` | show time |
@@ -342,7 +344,7 @@ Runs `omarchy-hyprland-workspace-layout-toggle`, then polls the workspace layout
### omarchy-toggle-scratch ### omarchy-toggle-scratch
```sh ```sh
omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home] omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home|lmstudio]
``` ```
| Profile | Special workspace | Home monitor | Residents (class → launch) | | Profile | Special workspace | Home monitor | Residents (class → launch) |
@@ -352,8 +354,9 @@ omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home]
| `fastmail` | `special:fastmail` | focused | `com.fastmail.Fastmail` → `gtk-launch com.fastmail.Fastmail` | | `fastmail` | `special:fastmail` | focused | `com.fastmail.Fastmail` → `gtk-launch com.fastmail.Fastmail` |
| `beeper` | `special:beeper` | focused | `Beeper` → `gtk-launch beeper` | | `beeper` | `special:beeper` | focused | `Beeper` → `gtk-launch beeper` |
| `home` | `special:home` | focused | `brave-home\.google\.com__.*` → `gtk-launch 'Google Home'` | | `home` | `special:home` | focused | `brave-home\.google\.com__.*` → `gtk-launch 'Google Home'` |
| `lmstudio` | `special:lmstudio` | focused | `ai\.elementlabs\.lmstudio` → `gtk-launch ai.elementlabs.lmstudio` |
If the workspace is visible, the script hides it. If not, it launches any resident with no window there, waits up to 8 s for the first new window, and reveals the workspace on the home monitor (or the focused one if the home monitor is absent). Launching only missing residents avoids duplicate web-app windows. Bound to `SUPER + CTRL + S`, `SUPER + A`, `SUPER + CTRL + ALT + E`, `SUPER + CTRL + ALT + M`, and `SUPER + CTRL + H`. If the workspace is visible, the script hides it. If not, it launches any resident with no window there, waits up to 8 s for the first new window, and reveals the workspace on the home monitor (or the focused one if the home monitor is absent). Launching only missing residents avoids duplicate web-app windows. Bound to `SUPER + CTRL + S`, `SUPER + A`, `SUPER + CTRL + ALT + E`, `SUPER + CTRL + ALT + M`, `SUPER + CTRL + H`, and `SUPER + CTRL + L`.
## Related ## Related
+1
View File
@@ -256,6 +256,7 @@ The prompt shows `❮` in green in vi normal mode (starship's `vimcmd_symbol`).
| --- | --- | --- | | --- | --- | --- |
| `ccc.fish` | `ccc` | Typer-generated: calls `ccc` with `_CCC_COMPLETE=complete_fish` | | `ccc.fish` | `ccc` | Typer-generated: calls `ccc` with `_CCC_COMPLETE=complete_fish` |
| `grok.fish` | `grok` | Static clap-style completion script (global options, subcommands) | | `grok.fish` | `grok` | Static clap-style completion script (global options, subcommands) |
| `mise.fish` | `mise` | `mise completion fish \| source` (generated at load time) |
| `nvm.fish` | `nvm` | From the nvm.fish plugin | | `nvm.fish` | `nvm` | From the nvm.fish plugin |
| `omp.fish` | `omp` | `omp completions fish \| source` (generated at load time) | | `omp.fish` | `omp` | `omp completions fish \| source` (generated at load time) |
| `ostt.fish` | `ostt` | Static clap-style completion script | | `ostt.fish` | `ostt` | Static clap-style completion script |
+21 -2
View File
@@ -150,9 +150,18 @@ Runs `ghq get <repo>`, with its output sent to stderr. It then works out the on-
Accepts `https://…`, `ssh://…`, `git@host:user/repo`, `host/user/repo`, or `user/repo`. Accepts `https://…`, `ssh://…`, `git@host:user/repo`, `host/user/repo`, or `user/repo`.
**Mirrors.** After the clone, it sets up a [prad.codes](/tools/git) mirror when the repo has none. All of it talks on stderr and never fails the clone:
| Cloned from | Mirror | Skipped when |
| --- | --- | --- |
| `github.com/<owner>/<name>` | Pull mirror into a prad.codes org, every 8h (`tea repos migrate --mirror --mirror-interval 8h`, authenticated with `gh auth token`, private if the GitHub repo is). `gum choose` picks the org, preselected from GitHub's primary language: Go → `go`, Python → `py`, Rust → `rs`, Dart → `dart`, TeX → `tex`, JS/TS/Astro/Svelte/Vue/HTML/CSS → `js`, Markdown/MDX → `md`, anything else → `env`. Esc skips | You are not an admin of the GitHub repo, or a prad.codes repo already pulls from it (`original_url`) |
| `prad.codes/<org>/<name>` | Push mirror to `github.com/<you>/<name>` on every push and every 8h, with a first sync right away. After a `gum confirm`, it creates the GitHub repo (same visibility) if missing; an existing one is overwritten by the mirror pushes, which the prompt says | The repo is itself a pull mirror, or already has a push mirror |
Both mirrors store your `gh` OAuth token in prad.codes as the remote credential. Without a terminal (no `gum` prompt), nothing is mirrored.
**Used by:** the `ghg` shell function, which cds into the path, clears the screen, and runs `onefetch`. The fish function `gtm` also uses it to clone migrated repos. **Used by:** the `ghg` shell function, which cds into the path, clears the screen, and runs `onefetch`. The fish function `gtm` also uses it to clone migrated repos.
**Needs:** `ghq` (`GHQ_ROOT=~/Code`, exported by fish, zsh, and bash), optional `zoxide`. **Needs:** `ghq` (`GHQ_ROOT=~/Code`, exported by fish, zsh, and bash), optional `zoxide`. Mirrors need `gh`, `tea` (logged in to prad.codes), `jq`, and `gum`; without any of them the step is skipped.
## ghn ## ghn
@@ -352,6 +361,8 @@ A task runner that searches every task source in the project and lets you pick o
pj-palette pj-palette
pj-palette --run KIND TARGET ROOT RUNNER pj-palette --run KIND TARGET ROOT RUNNER
pj-palette --cmd ROOT CMD… pj-palette --cmd ROOT CMD…
pj-palette --bg NAME [ROOT CMD…]
pj-palette --omp
``` ```
The source of the **Project Tasks** [tmux command palette](/terminal/tmux#command-palette) (`palettes/tasks.json`). Open it with tmux `prefix t`, Ghostty <kbd>ctrl+shift+p</kbd> or <kbd>ctrl+shift+space</kbd>, or the "Project Tasks" item in the main palette. The source of the **Project Tasks** [tmux command palette](/terminal/tmux#command-palette) (`palettes/tasks.json`). Open it with tmux `prefix t`, Ghostty <kbd>ctrl+shift+p</kbd> or <kbd>ctrl+shift+space</kbd>, or the "Project Tasks" item in the main palette.
@@ -360,15 +371,23 @@ With no arguments it reads the active pane's directory (`tmux display -p '#{pane
| Group | Items | Shown when | | Group | Items | Shown when |
| --- | --- | --- | | --- | --- | --- |
| Running | Every background task session still alive (any project), by session name | A long-running item was started and not stopped |
| Quick | `<pm> install` (alias `install`, `i`), `<pm> run dev` (`dev`) | `package.json` at the project root. The package manager comes from `packageManager`, then the lockfile, as in `pj` | | Quick | `<pm> install` (alias `install`, `i`), `<pm> run dev` (`dev`) | `package.json` at the project root. The package manager comes from `packageManager`, then the lockfile, as in `pj` |
| Quick | `uv sync` (`sync`), `uv run python` (`py`, `repl`) | `pyproject.toml` and `uv` installed | | Quick | `uv sync` (`sync`), `uv run python` (`py`, `repl`) | `pyproject.toml` and `uv` installed |
| Quick | `make` (default target; `m`, `build`) | A Makefile and `make` | | Quick | `make` (default target; `m`, `build`) | A Makefile and `make` |
| Quick | `go run .`, `cargo run` | `go.mod` / `Cargo.toml` and the toolchain | | Quick | `go run .`, `cargo run` | `go.mod` / `Cargo.toml` and the toolchain |
| Quick | `docker compose up` (`up`, `compose`) | `compose.y(a)ml` or `docker-compose.y(a)ml` at the project root and `docker` |
| Tasks: `<tool>` | Every record from `pj --list`: npm/bun/pnpm/yarn scripts, Make, just, mise, devbox, pyproject task tables and console scripts | Whatever `pj` finds walking up to the git root | | Tasks: `<tool>` | Every record from `pj --list`: npm/bun/pnpm/yarn scripts, Make, just, mise, devbox, pyproject task tables and console scripts | Whatever `pj` finds walking up to the git root |
The project root is the git toplevel, else the pane's directory. The project root is the git toplevel, else the pane's directory.
**Run.** Every item opens a full-width split along the bottom of the window (`split-window -v -f -l 25%`), 25% of its height, in the task's directory. The command is passed to `split-window` as separate arguments, so no shell reparses it. `--run` calls `pj --run`, which keeps pj's `openlogs` wrapping of `dev`/`start`/`serve`/`watch` tasks, and `--cmd` runs a quick action. While the command runs, <kbd>ctrl-c</kbd> stops only the command (the script traps `SIGINT`), so the output stays. Once the command exits, the script prints `[exit N] enter or ctrl-c to close`, and <kbd>enter</kbd> or <kbd>ctrl-c</kbd> closes the pane; other keys are ignored. **Run.** One-off items open a full-width split along the bottom of the window (`split-window -v -f -l 25%`), 25% of its height, in the task's directory. The command is passed to `split-window` as separate arguments, so no shell reparses it. `--run` calls `pj --run`, which keeps pj's `openlogs` wrapping of `dev`/`start`/`serve`/`watch` tasks, and `--cmd` runs a quick action. While the command runs, <kbd>ctrl-c</kbd> stops only the command (the script traps `SIGINT`), so the output stays. Once the command exits, the script prints `[exit N] enter or ctrl-c to close`, and <kbd>enter</kbd> or <kbd>ctrl-c</kbd> closes the pane; other keys are ignored.
**Background.** Long-running items, marked `· background`, are `<pm> run dev`, `docker compose up`, and pj tasks named `dev`/`start`/`serve`/`watch`/`preview` or with one of those as a `:` segment (`dev:web`, `api:dev`). They run through `--bg` in a detached session named `<session>:<tool-and-command>` (`leetcode:bun-run-dev`, `leetcode:docker-compose-up`), shown in a rounded 90%×80% popup attached to it. <kbd>q</kbd> detaches the popup and leaves the task running: the session's `key-table` is `pj-task`, which binds only `q`, so other keys reach the task instead of root-table bindings (prefix bindings still work). <kbd>ctrl-c</kbd> stops the task and ends the session, closing the popup. If the task exits on its own, the `[exit N]` prompt keeps its output until <kbd>enter</kbd>. Picking the item again, or its **Running** entry, reopens the popup instead of starting a second copy. The session has `status off`, `detach-on-destroy on`, and `@pj_task` set to the task's root, which is how the Running group finds it. Session names contain `:`, so the script targets them by id (`$N`).
**omp.** tmux `prefix a` (Ghostty <kbd>ctrl+shift+a</kbd>) runs `pj-palette --bg <session>:omp <pane cwd> omp`, so omp gets the same kind of background session and Running entry. Its popup docks in the bottom-right corner of the client, 35% wide and 60% tall. omp runs directly (no `[exit N]` prompt) and needs <kbd>q</kbd>, so its session uses the `pj-app` key table instead: <kbd>ctrl-c</kbd> hides the popup, <kbd>ctrl-q</kbd> kills the session (quits omp). `prefix a` inside any session with `@pj_task` also hides, which makes <kbd>ctrl+shift+a</kbd> a toggle. The popup border shows only the session name.
**omp palette.** `--omp` feeds `palettes/omp.json`, opened by the main palette's **omp** item. It lists every running `<session>:omp` across tmux sessions, with a green icon and `open`/`hidden` state: **This session** first (or a grey `not running · start in <cwd>` item that starts it), then **Running** for the other tmux sessions' agents, then a **Quit** item per session (`kill-session`).
**Needs:** `pj` and its dependencies, `jq`, tmux. **Needs:** `pj` and its dependencies, `jq`, tmux.
+1 -1
View File
@@ -65,7 +65,7 @@ Closing a window only detaches its session; the shells keep running in the tmux
| `ctrl+shift+f` | `prefix y` | Yazi in a new window, pane cwd | | `ctrl+shift+f` | `prefix y` | Yazi in a new window, pane cwd |
| `ctrl+shift+e` | `prefix e` | Neovim in a new window, pane cwd | | `ctrl+shift+e` | `prefix e` | Neovim in a new window, pane cwd |
| `ctrl+shift+g` | `prefix g` | Lazygit in a 90% popup, pane cwd | | `ctrl+shift+g` | `prefix g` | Lazygit in a 90% popup, pane cwd |
| `ctrl+shift+a` | `prefix a` | omp in a full-height split on the right, 33% of the window | | `ctrl+shift+a` | `prefix a` | Toggle omp in a background-session popup (`<session>:omp`), pane cwd |
| `ctrl+shift+w` | `prefix k` | Kill window (close tab) | | `ctrl+shift+w` | `prefix k` | Kill window (close tab) |
| `ctrl+shift+r` | `prefix r` | Rename window | | `ctrl+shift+r` | `prefix r` | Rename window |
| `ctrl+tab` / `ctrl+page_down` / `ctrl+shift+right` / `ctrl+shift+k` | `prefix n` | Next window | | `ctrl+tab` / `ctrl+page_down` / `ctrl+shift+right` / `ctrl+shift+k` | `prefix n` | Next window |
+5 -4
View File
@@ -42,10 +42,10 @@ These settings are what [yazi](/tools/yazi) needs inside tmux. `allow-passthroug
| `prefix q` | Reload config | | `prefix q` | Reload config |
| `prefix /` | Copy mode, then search upward | | `prefix /` | Copy mode, then search upward |
| `prefix o` | tmux-sessionx: fuzzy-find, switch to, create, rename (`ctrl-r`), or kill (`alt-backspace`) sessions, with a preview | | `prefix o` | tmux-sessionx: fuzzy-find, switch to, create, rename (`ctrl-r`), or kill (`alt-backspace`) sessions, with a preview |
| `prefix t` | Project Tasks palette ([`pj-palette`](/shell/scripts#pj-palette)): install/dev/sync/make quick actions plus every `pj` task, each run in a full-width bottom split (25% of the window) | | `prefix t` | Project Tasks palette ([`pj-palette`](/shell/scripts#pj-palette)): install/dev/sync/make/compose quick actions plus every `pj` task. One-off tasks run in a full-width bottom split (25% of the window); dev servers and `docker compose up` run in a background `<session>:<tool-and-command>` session shown in a popup (`q` hides, `ctrl-c` stops), listed under Running to reopen |
| `prefix y` / `prefix e` | Yazi / Neovim in a new window in the pane cwd (Ghostty `ctrl+shift+f` / `ctrl+shift+e`) | | `prefix y` / `prefix e` | Yazi / Neovim in a new window in the pane cwd (Ghostty `ctrl+shift+f` / `ctrl+shift+e`) |
| `prefix g` | Lazygit in a 90% popup in the pane cwd (Ghostty `ctrl+shift+g`) | | `prefix g` | Lazygit in a 90% popup in the pane cwd (Ghostty `ctrl+shift+g`) |
| `prefix a` | omp in a full-height split on the right, 33% of the window (Ghostty `ctrl+shift+a`) | | `prefix a` | omp in a background session `<session>:omp` (pane cwd), shown in a popup docked bottom-right (35% × 60%) and listed in the [omp palette](/shell/scripts#pj-palette) and under Running in the tasks palette. In the popup, `ctrl-c` hides it and `ctrl-q` quits omp. Inside any background-task popup, `prefix a` hides it too, so Ghostty `ctrl+shift+a` toggles omp |
| `prefix h` / `prefix v` | Split below / right, pane cwd | | `prefix h` / `prefix v` | Split below / right, pane cwd |
| `prefix x` | Kill pane | | `prefix x` | Kill pane |
| `S-Enter` (no prefix) | Sends `ESC[13;2u` to the pane unchanged. With `extended-keys on`, tmux would otherwise turn Shift+Enter into plain Enter for apps that never requested extended keys, and coding agents (omp, Claude Code) would submit instead of inserting a newline | | `S-Enter` (no prefix) | Sends `ESC[13;2u` to the pane unchanged. With `extended-keys on`, tmux would otherwise turn Shift+Enter into plain Enter for apps that never requested extended keys, and coding agents (omp, Claude Code) would submit instead of inserting a newline |
@@ -96,8 +96,9 @@ Labels longer than 27 characters end in `…`. The icon comes from tmux-nerd-fon
| File | Contents | | File | Contents |
| --- | --- | | --- | --- |
| `commands.json` | The kitty launchers ported as palette items: **Lazygit** (90% popup in the pane's cwd; kitty `ctrl+shift+g`), **Yazi** (new window; kitty `ctrl+shift+f`), **Neovim Split** (nvim in a split to the left, 60% of the pane; alias `nv`; kitty `ctrl+shift+e`), **Shell History (atuin)** (`atuin-pick` in a 25% split below; kitty `ctrl+shift+p`). Also **btop** (80% popup; aliases `top`, `htop`), **Lazydocker** (90% popup in the pane's cwd; alias `dd`), **mandible** (80% popup running [`mandible-pick`](/shell/scripts#mandible-pick); alias `man`), **television** (`tv channels`, the [channel picker](/tools/television), in a new window in the pane's cwd; alias `tv`), **gitframe** (git diff viewer in a new window in the pane's cwd; alias `gf`), **omp** (the omp agent in a full-height split on the right, 33% of the window; aliases `agent`, `ai`), **Project Tasks** (opens the tasks palette), **Sessions (sessionx)**, **Save Sessions**, and **Restore Sessions** (resurrect). Programs are started with `exec`, so the tab icon is the program's, not the shell's | | `commands.json` | The kitty launchers ported as palette items: **Lazygit** (90% popup in the pane's cwd; kitty `ctrl+shift+g`), **Yazi** (new window; kitty `ctrl+shift+f`), **Neovim Split** (nvim in a split to the left, 60% of the pane; alias `nv`; kitty `ctrl+shift+e`), **Shell History (atuin)** (`atuin-pick` in a 25% split below; kitty `ctrl+shift+p`). Also **btop** (80% popup; aliases `top`, `htop`), **Lazydocker** (90% popup in the pane's cwd; alias `dd`), **mandible** (80% popup running [`mandible-pick`](/shell/scripts#mandible-pick); alias `man`), **television** (`tv channels`, the [channel picker](/tools/television), in a new window in the pane's cwd; alias `tv`), **gitframe** (git diff viewer in a new window in the pane's cwd; alias `gf`), **omp** (opens the omp palette; aliases `agent`, `ai`), **Project Tasks** (opens the tasks palette), **Sessions (sessionx)**, **Save Sessions**, and **Restore Sessions** (resurrect). Programs are started with `exec`, so the tab icon is the program's, not the shell's |
| `palettes/tasks.json` | The **Project Tasks** palette (`prefix t`, Ghostty `ctrl+shift+p` / `ctrl+shift+space`), fed by [`pj-palette`](/shell/scripts#pj-palette): `bun install`, `bun run dev`, `uv sync`, `uv run python`, `make`, `go run .`, `cargo run` as Quick items when the project has them, then every task `pj` finds, grouped by tool. Each runs in a full-width split along the bottom (25% of the window) in its directory. `ctrl-c` stops a running task; once it has exited, `enter` or `ctrl-c` closes the pane | | `palettes/tasks.json` | The **Project Tasks** palette (`prefix t`, Ghostty `ctrl+shift+p` / `ctrl+shift+space`), fed by [`pj-palette`](/shell/scripts#pj-palette): running background tasks under Running, then `bun install`, `bun run dev`, `uv sync`, `uv run python`, `make`, `go run .`, `cargo run`, `docker compose up` as Quick items when the project has them, then every task `pj` finds, grouped by tool. One-off tasks run in a full-width split along the bottom (25% of the window) in their directory; `ctrl-c` stops a running task, and once it has exited, `enter` or `ctrl-c` closes the pane. Dev servers (`dev`/`start`/`serve`/`watch`/`preview` tasks) and `docker compose up` run in a background session `<session>:<tool-and-command>` shown in a popup: `q` hides it with the task still running, `ctrl-c` stops the task and closes it |
| `palettes/omp.json` | The **omp** palette, fed by [`pj-palette --omp`](/shell/scripts#pj-palette): this tmux session's `<session>:omp` (open it, or start it when not running), every other tmux session's running omp, and a Quit item per session. Running agents have a green icon and show `open` or `hidden` |
| `hidden.json` | Hides the built-in **Switch Theme...** item, and with it the Appearance section: the palette stays on the fixed cyberdream `theme.json` | | `hidden.json` | Hides the built-in **Switch Theme...** item, and with it the Appearance section: the palette stays on the fixed cyberdream `theme.json` |
| `shortcuts.json` | Ghostty `ctrl+shift` chords shown next to the built-in items they trigger (New Window → `Ctrl+Shift+T`, …) | | `shortcuts.json` | Ghostty `ctrl+shift` chords shown next to the built-in items they trigger (New Window → `Ctrl+Shift+T`, …) |
| `theme.json` | Cyberdream: bg `#16181a`, panel `#1e2124`, selected `#243348` (the active-tab plate), muted `#7b8496`, accent `#5ea1ff` | | `theme.json` | Cyberdream: bg `#16181a`, panel `#1e2124`, selected `#243348` (the active-tab plate), muted `#7b8496`, accent `#5ea1ff` |
+1 -1
View File
@@ -107,6 +107,6 @@ These git-adjacent commands live in `~/.local/bin` and the shell function direct
| `gdc` | Asks for confirmation with `gum confirm`, then runs `git reset --hard HEAD` and `git clean -fd`. | | `gdc` | Asks for confirmation with `gum confirm`, then runs `git reset --hard HEAD` and `git clean -fd`. |
| `gop` | Opens a repo's web page for any forge whose layout is `https://<host>/<owner>/<repo>`; defaults to `origin`, then `upstream`. Also the <kbd>ctrl-b</kbd> action of the `ghq-repos` [television](/tools/television#ghq-repos) cable. | | `gop` | Opens a repo's web page for any forge whose layout is `https://<host>/<owner>/<repo>`; defaults to `origin`, then `upstream`. Also the <kbd>ctrl-b</kbd> action of the `ghq-repos` [television](/tools/television#ghq-repos) cable. |
| `git-summary` | Aggregates every `AGENTS.md` in the current repo through `code2prompt`. | | `git-summary` | Aggregates every `AGENTS.md` in the current repo through `code2prompt`. |
| `ghg` / `ghg-clone` | `ghq get` a repo and `cd` into the clone. | | `ghg` / `ghg-clone` | `ghq get` a repo and `cd` into the clone; sets up a prad.codes pull mirror (your GitHub repos) or GitHub push mirror (prad.codes repos) when none exists. |
| `ghn` | Creates a private repo on prad.codes (via `tea`) or github.com (via `gh`), clones it with ghq, and `cd`s into it. | | `ghn` | Creates a private repo on prad.codes (via `tea`) or github.com (via `gh`), clones it with ghq, and `cd`s into it. |
| `gho` | Picks a ghq repo or Obsidian vault with the `ghq-repos` [television](/tools/television#ghq-repos) cable and `cd`s into it. | | `gho` | Picks a ghq repo or Obsidian vault with the `ghq-repos` [television](/tools/television#ghq-repos) cable and `cd`s into it. |
+1 -1
View File
@@ -86,7 +86,7 @@ Entries are `name<TAB>path`: tv shows and matches the name and prints the path.
| Source | Entries | Sort | | Source | Entries | Sort |
| --- | --- | --- | | --- | --- | --- |
| Repos | `ghq list` under `ghq root`, plus the chezmoi source (`~/.local/share/chezmoi`) listed as `github.com/prdlk/dotfiles` | `mtime-sort --repo` | | Repos | `ghq list` under `ghq root`, plus two checkouts outside it: the chezmoi source (`~/.local/share/chezmoi`) as `github.com/prdlk/dotfiles` and the nvim config (`~/.config/nvim`) as `github.com/prdlk/nvim`. Those two win over a ghq clone with the same name | `mtime-sort --repo` |
| Vaults | Every directory under `~/Documents/Obsidian` (depth 3) that contains `.obsidian`, named relative to that directory | `mtime-sort` | | Vaults | Every directory under `~/Documents/Obsidian` (depth 3) that contains `.obsidian`, named relative to that directory | `mtime-sort` |
**Preview.** Two commands; <kbd>ctrl-f</kbd> cycles. The default is the commit graph for git directories: `git log --graph --all`, newest 300 commits, as short hash, relative date, subject and refs. The second is `onefetch --no-art`. Non-git directories show `eza --tree --level 2` (falling back to `ls -la`) in both. **Preview.** Two commands; <kbd>ctrl-f</kbd> cycles. The default is the commit graph for git directories: `git log --graph --all`, newest 300 commits, as short hash, relative date, subject and refs. The second is `onefetch --no-art`. Non-git directories show `eza --tree --level 2` (falling back to `ls -la`) in both.
+5
View File
@@ -76,3 +76,8 @@ command -v atuin >/dev/null 2>&1 && eval "$(atuin init bash)"
# >>> termium >>> # >>> termium >>>
case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac
# <<< termium <<< # <<< termium <<<
# Added by LM Studio CLI (lms)
export PATH="$PATH:/home/prad/.lmstudio/bin"
# End of LM Studio CLI section
+1
View File
@@ -0,0 +1 @@
mise completion fish | source
+5
View File
@@ -97,3 +97,8 @@ end
bind "?" _atuin_ai_question_mark bind "?" _atuin_ai_question_mark
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv fish)" eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv fish)"
# Added by LM Studio CLI (lms)
set -gx PATH $PATH /home/prad/.lmstudio/bin
# End of LM Studio CLI section
-1
View File
@@ -1,7 +1,6 @@
# Font Configuration # Font Configuration
font-size = 11 font-size = 11
font-family = VictorMono Nerd Font font-family = VictorMono Nerd Font
adjust-cell-height = 10%
background-blur = true background-blur = true
# Window Configuration # Window Configuration
+12 -8
View File
@@ -171,14 +171,17 @@ o.bind("SUPER + CTRL + ALT + X", "Workspace x", hl.dsp.focus({ workspace = "name
o.bind("SUPER + R", "Cycle window size", "omarchy-cycle-window-size") o.bind("SUPER + R", "Cycle window size", "omarchy-cycle-window-size")
o.bind("SUPER + SHIFT + R", "Cycle window size back", "omarchy-cycle-window-size prev") o.bind("SUPER + SHIFT + R", "Cycle window size back", "omarchy-cycle-window-size prev")
-- Alt+Shift+hjkl. Hyprland resizes by pixels, not by percent, so 100 px -- Hyprland resizes by pixels, not by percent, so 100 px replaces the niri
-- replaces the niri 10%. -- 10%. ALT + SHIFT + H / L step the width (ALT + SHIFT + J / K move the
-- window between monitors, above).
o.bind("ALT + SHIFT + H", "Shrink window width", hl.dsp.window.resize({ x = -100, y = 0, relative = true }))
o.bind("ALT + SHIFT + L", "Grow window width", hl.dsp.window.resize({ x = 100, y = 0, relative = true }))
hl.unbind("SUPER + CTRL + H") -- was: toggle window gaps (now Google Home; SUPER + comma shrinks width) hl.unbind("SUPER + CTRL + H") -- was: toggle window gaps (now Google Home; SUPER + comma shrinks width)
hl.unbind("SUPER + CTRL + L") -- was: toggle window gaps hl.unbind("SUPER + CTRL + L") -- was: toggle window gaps (now LM Studio; SUPER + period grows width)
hl.unbind("SUPER + CTRL + K") -- was: toggle window gaps hl.unbind("SUPER + CTRL + K") -- was: toggle window gaps
hl.unbind("SUPER + CTRL + J") -- was: toggle window gaps hl.unbind("SUPER + CTRL + J") -- was: toggle window gaps
o.bind("SUPER + CTRL + L", "Grow window width", hl.dsp.window.resize({ x = 100, y = 0, relative = true }))
o.bind("SUPER + CTRL + K", "Shrink window height", hl.dsp.window.resize({ x = 0, y = -100, relative = true })) o.bind("SUPER + CTRL + K", "Shrink window height", hl.dsp.window.resize({ x = 0, y = -100, relative = true }))
o.bind("SUPER + CTRL + J", "Grow window height", hl.dsp.window.resize({ x = 0, y = 100, relative = true })) o.bind("SUPER + CTRL + J", "Grow window height", hl.dsp.window.resize({ x = 0, y = 100, relative = true }))
@@ -238,13 +241,14 @@ o.bind("SUPER + CTRL + ALT + K", "Browsers", "omarchy-cycle-browser")
o.bind("SUPER + CTRL + ALT + G", "Ghostty", "omarchy-cycle ghostty") o.bind("SUPER + CTRL + ALT + G", "Ghostty", "omarchy-cycle ghostty")
o.bind("SUPER + CTRL + ALT + O", "Obsidian", "omarchy-cycle obsidian") o.bind("SUPER + CTRL + ALT + O", "Obsidian", "omarchy-cycle obsidian")
o.bind("SUPER + CTRL + ALT + F", "File manager", "omarchy-launch-or-focus nautilus \"gtk-launch org.gnome.Nautilus\"") o.bind("SUPER + CTRL + ALT + F", "File manager", "omarchy-launch-or-focus nautilus \"gtk-launch org.gnome.Nautilus\"")
-- Fastmail and Beeper each live on their own scratch workspace (window rules -- Fastmail, Beeper, Google Home and LM Studio each live on their own scratch
-- in hypr/hyprland.lua); the toggle reveals it on the focused monitor and -- workspace (window rules in hypr/hyprland.lua); the toggle reveals it on the
-- starts the app only when no window of it exists -- the web-app launcher -- focused monitor and starts the app only when no window of it exists -- the
-- would otherwise open a second window every time. -- web-app launcher would otherwise open a second window every time.
o.bind("SUPER + CTRL + ALT + E", "Fastmail", "omarchy-toggle-scratch fastmail") o.bind("SUPER + CTRL + ALT + E", "Fastmail", "omarchy-toggle-scratch fastmail")
o.bind("SUPER + CTRL + ALT + M", "Beeper", "omarchy-toggle-scratch beeper") o.bind("SUPER + CTRL + ALT + M", "Beeper", "omarchy-toggle-scratch beeper")
o.bind("SUPER + CTRL + H", "Google Home", "omarchy-toggle-scratch home") o.bind("SUPER + CTRL + H", "Google Home", "omarchy-toggle-scratch home")
o.bind("SUPER + CTRL + L", "LM Studio", "omarchy-toggle-scratch lmstudio")
-- YouTube is a floating pinned window in the bottom-left corner (window rule -- YouTube is a floating pinned window in the bottom-left corner (window rule
-- in hypr/hyprland.lua). The summon launches it when no window exists, -- in hypr/hyprland.lua). The summon launches it when no window exists,
-- otherwise carries the window to the focused monitor and focuses it. -- otherwise carries the window to the focused monitor and focuses it.
+2
View File
@@ -69,6 +69,8 @@ o.window([[^com\.fastmail\.Fastmail$]], { workspace = "special:fastmail silent"
o.window([[^Beeper$]], { workspace = "special:beeper silent" }) o.window([[^Beeper$]], { workspace = "special:beeper silent" })
-- SUPER + CTRL + H: Google Home (Brave web app), on the focused monitor. -- SUPER + CTRL + H: Google Home (Brave web app), on the focused monitor.
o.window([[^brave-home\.google\.com__.*]], { workspace = "special:home silent" }) o.window([[^brave-home\.google\.com__.*]], { workspace = "special:home silent" })
-- SUPER + CTRL + L: LM Studio, on the focused monitor.
o.window([[^ai\.elementlabs\.lmstudio$]], { workspace = "special:lmstudio silent" })
-- SUPER + CTRL + ALT + Y / SUPER + SHIFT + Y: the YouTube web app, summoned -- SUPER + CTRL + ALT + Y / SUPER + SHIFT + Y: the YouTube web app, summoned
-- by ~/.local/bin/omarchy-summon. Not a scratch workspace: a floating, -- by ~/.local/bin/omarchy-summon. Not a scratch workspace: a floating,
+2 -3
View File
@@ -68,11 +68,10 @@
{ {
"icon": "", "icon": "",
"title": "omp", "title": "omp",
"shortcut": "Ctrl+Shift+A", "description": "omp sessions: this session's agent, every running one, quit",
"description": "omp agent in a full-height split on the right, 33%",
"category": "Tools", "category": "Tools",
"aliases": ["agent", "ai"], "aliases": ["agent", "ai"],
"action": { "tmux": "split-window -h -f -l 33% -c '#{pane_current_path}' 'exec omp'" } "action": { "palette": "omp" }
}, },
{ {
"icon": "", "icon": "",
@@ -0,0 +1,6 @@
{
"title": "omp",
"command": "~/.local/bin/pj-palette --omp",
"grouped": true,
"emptyText": "No omp sessions"
}
+7 -2
View File
@@ -197,7 +197,8 @@ set -g @plugin 'eduwass/tmux-palette'
# shortcut labels) lives in ~/.config/tmux-palette/. # shortcut labels) lives in ~/.config/tmux-palette/.
set -g @palette-key 'C-Space' set -g @palette-key 'C-Space'
# pj's task runner as a palette (pj-palette): quick actions + project tasks, # pj's task runner as a palette (pj-palette): quick actions + project tasks,
# each run in a bottom split. prefix + t (Ghostty ctrl+shift+p / +space). # one-offs in a bottom split, dev servers / compose up in a background
# <session>:<task> session popup. prefix + t (Ghostty ctrl+shift+p / +space).
bind -N "Project tasks (pj)" t run-shell "~/.config/tmux/plugins/tmux-palette/bin/tmux-palette.sh tasks" bind -N "Project tasks (pj)" t run-shell "~/.config/tmux/plugins/tmux-palette/bin/tmux-palette.sh tasks"
# Direct launchers, the kitty ctrl+shift chords (Ghostty sends prefix + key). # Direct launchers, the kitty ctrl+shift chords (Ghostty sends prefix + key).
@@ -205,7 +206,11 @@ bind -N "Project tasks (pj)" t run-shell "~/.config/tmux/plugins/tmux-palette/bi
bind -N "Yazi in a new window" y new-window -c "#{pane_current_path}" "exec yazi" bind -N "Yazi in a new window" y new-window -c "#{pane_current_path}" "exec yazi"
bind -N "Neovim in a new window" e new-window -c "#{pane_current_path}" "exec nvim" bind -N "Neovim in a new window" e new-window -c "#{pane_current_path}" "exec nvim"
bind -N "Lazygit popup" g display-popup -E -w 90% -h 90% -d "#{pane_current_path}" lazygit bind -N "Lazygit popup" g display-popup -E -w 90% -h 90% -d "#{pane_current_path}" lazygit
bind -N "omp in a right split (33%)" a split-window -h -f -l 33% -c "#{pane_current_path}" "exec omp" # omp in a background session <session>:omp shown in a popup (pj-palette --bg,
# listed under Running in the tasks palette). Inside any such popup, prefix + a
# hides it instead, so ctrl+shift+a toggles.
bind -N "omp popup (toggle)" a if -F "#{@pj_task}" detach-client \
'run-shell -b "~/.local/bin/pj-palette --bg #{q:session_name}:omp #{q:pane_current_path} omp"'
# sessionx: fuzzy session switcher/creator on prefix + o (Ghostty # sessionx: fuzzy session switcher/creator on prefix + o (Ghostty
# ctrl+shift+o). A query with no match goes to zoxide; uses fzf --tmux. # ctrl+shift+o). A query with no match goes to zoxide; uses fzf --tmux.
+82
View File
@@ -2,6 +2,13 @@
# ghq get + resolve the on-disk clone path; prints the repo directory. # ghq get + resolve the on-disk clone path; prints the repo directory.
# Shell wrappers (fish/bash/zsh `ghg` functions) cd into the printed path — # Shell wrappers (fish/bash/zsh `ghg` functions) cd into the printed path —
# a child process can't change the caller's cwd. # a child process can't change the caller's cwd.
#
# Then sets up a prad.codes mirror when the repo has none (best effort; the
# clone never fails over it):
# github.com/<owner>/<name> you admin → pull mirror into a prad.codes org
# (picked with gum, guessed from the language; esc skips), every 8h
# prad.codes/<org>/<name> that isn't itself a mirror → push mirror to
# github.com/<you>/<name> (created if missing), on every push and every 8h
repo=$1 repo=$1
if [ -z "$repo" ]; then if [ -z "$repo" ]; then
echo "usage: ghg-clone <repo>" >&2 echo "usage: ghg-clone <repo>" >&2
@@ -31,4 +38,79 @@ if [ ! -d "$dir" ]; then
fi fi
command -v zoxide >/dev/null 2>&1 && zoxide add "$dir" command -v zoxide >/dev/null 2>&1 && zoxide add "$dir"
# ── Mirrors ───────────────────────────────────────────────────────────────
# Everything below talks on stderr; stdout carries only the final path.
have() { command -v "$1" >/dev/null 2>&1; }
# prad.codes orgs are per language; guess one from GitHub's primary language.
lang_org() {
case $1 in
Go) echo go ;;
Python | "Jupyter Notebook") echo py ;;
Rust) echo rs ;;
Dart) echo dart ;;
TeX) echo tex ;;
JavaScript | TypeScript | Astro | Svelte | Vue | HTML | CSS) echo js ;;
Markdown | MDX) echo md ;;
*) echo env ;; # Lua, Shell, no code at all (dotfiles, configs)
esac
}
pull_mirror() { # owner name
info=$(gh repo view "$1/$2" --json viewerPermission,isPrivate,primaryLanguage \
-q '[.viewerPermission, .isPrivate, .primaryLanguage.name // ""] | @tsv' 2>/dev/null) || return 0
perm=$(printf '%s\n' "$info" | cut -f1)
private=$(printf '%s\n' "$info" | cut -f2)
lang=$(printf '%s\n' "$info" | cut -f3)
[ "$perm" = ADMIN ] || return 0 # someone else's repo: just a clone
# tea api exits 0 even on errors, hence jq -e on every response.
tea api "/api/v1/repos/search?q=$2&limit=50" | jq -e --arg u "github.com/$1/$2" \
'any(.data[]; (.original_url // "") | ascii_downcase | sub("(\\.git)?/?$"; "") | endswith($u | ascii_downcase))' \
>/dev/null && return 0
org=$(tea api '/api/v1/orgs?limit=50' | jq -r '.[].username' |
gum choose --header "Pull-mirror github.com/$1/$2 into which prad.codes org? (esc skips)" \
--selected "$(lang_org "$lang")") || return 0
[ -n "$org" ] || return 0
echo "→ pull mirror: github.com/$1/$2 → prad.codes/$org/$2 (every 8h)" >&2
priv=
[ "$private" = true ] && priv=--private
# shellcheck disable=SC2086 # $priv is one optional flag
tea repos migrate --name "$2" --owner "$org" --clone-url "https://github.com/$1/$2.git" \
--service git --mirror --mirror-interval 8h --auth-token "$(gh auth token)" $priv >&2 ||
echo "ghg: pull mirror failed" >&2
}
push_mirror() { # org name
repo=$(tea api "/api/v1/repos/$1/$2")
printf '%s' "$repo" | jq -e '.id and (.mirror | not)' >/dev/null || return 0
tea api "/api/v1/repos/$1/$2/push_mirrors" | jq -e 'length == 0' >/dev/null || return 0
me=$(gh api user -q .login 2>/dev/null) || return 0
if gh repo view "$me/$2" >/dev/null 2>&1; then
gum confirm "Push-mirror prad.codes/$1/$2 to the existing github.com/$me/$2? Mirror pushes overwrite it." || return 0
else
gum confirm "Push-mirror prad.codes/$1/$2 to a new github.com/$me/$2?" || return 0
vis=--public
[ "$(printf '%s' "$repo" | jq -r .private)" = true ] && vis=--private
gh repo create "$me/$2" "$vis" --description "Mirror of https://prad.codes/$1/$2" >&2 || return 0
fi
if jq -n --arg url "https://github.com/$me/$2.git" --arg user "$me" --arg pass "$(gh auth token)" \
'{remote_address: $url, remote_username: $user, remote_password: $pass,
interval: "8h0m0s", sync_on_commit: true}' |
tea api -d @- "/api/v1/repos/$1/$2/push_mirrors" | jq -e .remote_name >/dev/null; then
tea api -X POST "/api/v1/repos/$1/$2/push_mirrors-sync" >/dev/null # first push now
echo "→ push mirror: prad.codes/$1/$2 → github.com/$me/$2 (on push + every 8h)" >&2
else
echo "ghg: push mirror failed" >&2
fi
}
if have gh && have tea && have jq && have gum; then
owner=${rel#*/} name=${owner#*/} owner=${owner%%/*}
case $rel in
github.com/*/*) pull_mirror "$owner" "$name" ;;
prad.codes/*/*) push_mirror "$owner" "$name" ;;
esac
fi
printf '%s\n' "$dir" printf '%s\n' "$dir"
@@ -1,10 +1,10 @@
#!/bin/bash #!/bin/bash
# omarchy:summary=Toggle a scratch workspace (scratchpad, claude, fastmail, beeper, home), starting its resident apps when any is missing. # omarchy:summary=Toggle a scratch workspace (scratchpad, claude, fastmail, beeper, home, lmstudio), starting its resident apps when any is missing.
set -uo pipefail set -uo pipefail
# Usage: omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home] # Usage: omarchy-toggle-scratch [scratchpad|claude|fastmail|beeper|home|lmstudio]
# #
# Each profile is a special workspace, the monitor it opens on, and its # Each profile is a special workspace, the monitor it opens on, and its
# resident apps (class pattern -> launch command; the pattern is an anchored # resident apps (class pattern -> launch command; the pattern is an anchored
@@ -43,8 +43,12 @@ home)
NAME="home" NAME="home"
MEMBERS=("brave-home\.google\.com__.*|gtk-launch 'Google Home'") MEMBERS=("brave-home\.google\.com__.*|gtk-launch 'Google Home'")
;; ;;
lmstudio)
NAME="lmstudio"
MEMBERS=("ai\.elementlabs\.lmstudio|gtk-launch ai.elementlabs.lmstudio")
;;
*) *)
echo "usage: ${0##*/} [scratchpad|claude|fastmail|beeper|home]" >&2 echo "usage: ${0##*/} [scratchpad|claude|fastmail|beeper|home|lmstudio]" >&2
exit 2 exit 2
;; ;;
esac esac
+141 -30
View File
@@ -3,27 +3,45 @@
# ~/.config/tmux-palette/palettes/tasks.json). # ~/.config/tmux-palette/palettes/tasks.json).
# #
# pj-palette print palette items (JSON) for the active pane's # pj-palette print palette items (JSON) for the active pane's
# project: quick actions (install, dev, sync, make, # project: running background tasks, quick actions
# …) and every task `pj --list` finds # (install, dev, sync, make, compose up, …) and
# every task `pj --list` finds
# pj-palette --run KIND TARGET ROOT RUNNER # pj-palette --run KIND TARGET ROOT RUNNER
# run a pj task (the item's action) # run a pj task (the item's action)
# pj-palette --cmd ROOT CMD… # pj-palette --cmd ROOT CMD…
# run a quick-action command in ROOT # run a quick-action command in ROOT
# pj-palette --bg NAME [ROOT CMD…]
# show background session NAME in a popup, first
# starting CMD… in ROOT if it isn't running
# pj-palette --omp print the omp palette's items: this tmux
# session's omp (open or start), every other
# running <session>:omp, and Quit items
# #
# Every item opens a full-width split along the bottom of the window (25% of # One-off items open a full-width split along the bottom of the window (25% of
# its height) in the project root. The command runs in the foreground; ctrl-c # its height) in the project root. The command runs in the foreground; ctrl-c
# stops it (the output stays). Once it has exited, enter or ctrl-c closes the # stops it (the output stays). Once it has exited, enter or ctrl-c closes the
# pane. # pane.
#
# Long-running items (dev/start/serve/watch/preview tasks, compose up) run in a
# detached session named <session>:<tool-and-command> (leetcode:bun-run-dev),
# shown in a popup: q hides the popup and leaves the task running, ctrl-c
# stops the task and ends its session. Running ones are listed first, under
# Running, to bring them back. tmux.conf's prefix + a starts omp the same way
# (<session>:omp); omp needs q, so its popup hides with ctrl-c (or prefix + a)
# and ctrl-q quits it.
set -uo pipefail set -uo pipefail
self=$(readlink -f "$0") self=$(readlink -f "$0")
# While the task runs, ctrl-c reaches only the task: with SIGINT trapped, # While the task runs, ctrl-c reaches only the task: with SIGINT trapped,
# bash survives the child's death instead of exiting along with it. # bash survives the child's death instead of exiting along with it.
trap : INT interrupted=
trap 'interrupted=1' INT
finish() { finish() {
local status=$1 key local status=$1 key
# A background task stopped with ctrl-c ends its session (and popup).
[ -n "${PJ_BG-}" ] && [ -n "$interrupted" ] && exit 0
printf '\n\033[2m[exit %s] enter or ctrl-c to close\033[0m' "$status" printf '\n\033[2m[exit %s] enter or ctrl-c to close\033[0m' "$status"
trap 'exit 0' INT trap 'exit 0' INT
while IFS= read -rsn1 key; do while IFS= read -rsn1 key; do
@@ -32,6 +50,16 @@ finish() {
exit 0 exit 0
} }
# Id of session NAME. Background session names contain ':', which -t reads
# as session:window, so they are targeted by id.
session_id() {
local id name
while IFS=$'\t' read -r id name; do
[ "$name" = "$1" ] && { printf '%s' "$id"; return 0; }
done < <(tmux list-sessions -F $'#{session_id}\t#{session_name}' 2>/dev/null)
return 1
}
case ${1-} in case ${1-} in
--run) --run)
shift shift
@@ -45,10 +73,42 @@ case ${1-} in
"$@" "$@"
finish $? finish $?
;; ;;
--bg)
name=$2
if ! id=$(session_id "$name"); then
[ $# -ge 4 ] || exit 0 # a Running item whose task has since ended
root=$3
shift 3
id=$(tmux new-session -d -P -F '#{session_id}' -s "$name" -c "$root" -e PJ_BG=1 "$@") || exit 1
# @pj_task marks the session for the Running list (and for tmux.conf's
# prefix + a, which hides any of these popups).
tmux set -t "$id" @pj_task "$root" \; set -t "$id" status off \; \
set -t "$id" detach-on-destroy on
# Each session gets a key table for its hide/quit keys; other keys
# reach the program. A task's output takes no input, so q hides and
# ctrl-c stops the task. Interactive programs (omp) need q and typing,
# so ctrl-c hides and ctrl-q kills the session.
if [ "$1" = "$self" ]; then
tmux set -t "$id" key-table pj-task \; bind -T pj-task q detach-client
else
tmux set -t "$id" key-table pj-app \; bind -T pj-app C-c detach-client \; \
bind -T pj-app C-q kill-session
fi
fi
# Task output gets a big centered popup; interactive programs (omp) dock
# bottom-right beside the work. Positions past the client edge clamp to
# it (-x R would follow whichever pane tmux picks as current).
geometry=(-w 90% -h 80%)
[ "$(tmux display -p -t "$id" '#{key-table}')" = pj-app ] &&
geometry=(-w 35% -h 60% -x '#{client_width}' -y '#{client_height}')
tmux display-popup -E "${geometry[@]}" -b rounded -T " $name " "tmux attach -t '$id'"
exit 0
;;
esac esac
have() { command -v "$1" >/dev/null 2>&1; } have() { command -v "$1" >/dev/null 2>&1; }
cwd=$(tmux display -p '#{pane_current_path}' 2>/dev/null) || cwd=$PWD cwd=$(tmux display -p '#{pane_current_path}' 2>/dev/null) || cwd=$PWD
session=$(tmux display -p '#{session_name}' 2>/dev/null)
cd "$cwd" || exit 1 cd "$cwd" || exit 1
root=$(git rev-parse --show-toplevel 2>/dev/null) || root=$cwd root=$(git rev-parse --show-toplevel 2>/dev/null) || root=$cwd
@@ -65,29 +125,72 @@ icon_for() {
esac esac
} }
# tmux action that opens a full-width bottom split (25%) in DIR running ARGV # Tasks that run until stopped: dev/start/serve/watch/preview, also as a
# directly (no shell, so nothing is re-parsed by fish). The palette wrapper # namespace segment (dev:web, api:dev).
# evals the string in bash, hence %q. long_running() { [[ $1 =~ (^|:)(dev|start|serve|watch|preview)(:|$) ]]; }
split_action() { slug() { local s=${*,,}; s=${s//[^a-z0-9]/-}; tr -s - <<<"$s"; }
local dir=$1
shift # Item action (a bash line the palette evals) running `pj-palette ARGV…`:
local q # in a full-width bottom split (25%) in DIR, or, given NAME, in the background
q=$(printf '%q ' "$@") # session <session>:NAME shown in a popup. Argv goes to tmux as is (no shell,
printf 'split-window -v -f -l 25%% -c %q %s' "$dir" "$q" # so fish re-parses nothing).
task_action() { # dir name argv…
local dir=$1 name=$2
shift 2
if [ -n "$name" ]; then
printf '%q ' "$self" --bg "$session:$name" "$dir" "$self" "$@"
else
printf '%q ' tmux split-window -v -f -l 25% -c "$dir" "$self" "$@"
fi
} }
items=() items=()
item() { # category icon title description aliases(csv) tmux-action item() { # category icon title description aliases(csv) action [icon color]
items+=("$(jq -cn --arg c "$1" --arg i "$2" --arg t "$3" --arg d "$4" --arg a "$5" --arg x "$6" \ items+=("$(jq -cn --arg c "$1" --arg i "$2" --arg t "$3" --arg d "$4" --arg a "$5" --arg x "$6" --arg k "${7-}" \
'{category: $c, icon: $i, title: $t, description: $d, '{category: $c, icon: $i, title: $t, description: $d,
aliases: ($a | split(",") | map(select(. != ""))), action: {tmux: $x}}')") aliases: ($a | split(",") | map(select(. != ""))), action: {shell: $x}}
+ if $k == "" then {} else {iconColor: $k} end')")
} }
quick() { # icon title aliases dir argv… quick() { # icon title aliases background(0/1) dir argv…
local icon=$1 title=$2 aliases=$3 dir=$4 local icon=$1 title=$2 aliases=$3 dir=$5 name="" desc="in ${5/#$HOME/\~}"
shift 4 [ "$4" = 1 ] && { name=$(slug "$title"); desc+=" · background"; }
item Quick "$icon" "$title" "in ${dir/#$HOME/~}" "$aliases" "$(split_action "$dir" "$self" --cmd "$dir" "$@")" shift 5
item Quick "$icon" "$title" "$desc" "$aliases" "$(task_action "$dir" "$name" --cmd "$dir" "$@")"
} }
# ── omp palette (palettes/omp.json): the <session>:omp agent sessions ──────
if [ "${1-}" = --omp ]; then
here=$session:omp live='#5eff6c' idle='#7b8496'
omps=$(tmux list-sessions -F $'#{session_id}\t#{session_name}\t#{@pj_task}\t#{session_attached}' 2>/dev/null |
awk -F'\t' '$2 ~ /:omp$/ && $3 != ""')
state() { [ "$1" -gt 0 ] && printf open || printf hidden; }
found=
while IFS=$'\t' read -r id name dir attached; do
[ "$name" = "$here" ] || continue
found=1
item "This session" '' "$name" "$(state "$attached") · in ${dir/#$HOME/\~}" "" \
"$(printf '%q ' "$self" --bg "$name")" "$live"
done <<<"$omps"
[ -n "$found" ] || item "This session" '' "$here" "not running · start in ${cwd/#$HOME/\~}" "new" \
"$(printf '%q ' "$self" --bg "$here" "$cwd" omp)" "$idle"
while IFS=$'\t' read -r id name dir attached; do
[ -n "$name" ] && [ "$name" != "$here" ] || continue
item Running '' "$name" "$(state "$attached") · in ${dir/#$HOME/\~}" "" \
"$(printf '%q ' "$self" --bg "$name")" "$live"
done <<<"$omps"
while IFS=$'\t' read -r id name dir attached; do
[ -n "$name" ] && item Quit '' "Quit $name" "kill the session" "kill" \
"$(printf '%q ' tmux kill-session -t "$id")"
done <<<"$omps"
printf '%s\n' "${items[@]}" | jq -s .
exit 0
fi
# ── Background tasks still running (any project), to bring back ──────────
while IFS=$'\t' read -r name dir; do
[ -n "$dir" ] && item Running '' "$name" "in ${dir/#$HOME/\~}" "" "$(printf '%q ' "$self" --bg "$name")"
done < <(tmux list-sessions -F $'#{session_name}\t#{@pj_task}' 2>/dev/null)
# ── Quick actions, from the files at the project root ───────────────────── # ── Quick actions, from the files at the project root ─────────────────────
if [ -f "$root/package.json" ]; then if [ -f "$root/package.json" ]; then
pm=$(jq -r '.packageManager // empty' "$root/package.json" 2>/dev/null) pm=$(jq -r '.packageManager // empty' "$root/package.json" 2>/dev/null)
@@ -100,33 +203,41 @@ if [ -f "$root/package.json" ]; then
fi fi
fi fi
if have "$pm"; then if have "$pm"; then
quick "$(icon_for "$pm")" "$pm install" "install,i" "$root" "$pm" install quick "$(icon_for "$pm")" "$pm install" "install,i" 0 "$root" "$pm" install
jq -e '.scripts.dev' "$root/package.json" >/dev/null 2>&1 && jq -e '.scripts.dev' "$root/package.json" >/dev/null 2>&1 &&
quick "$(icon_for "$pm")" "$pm run dev" "dev" "$root" "$pm" run dev quick "$(icon_for "$pm")" "$pm run dev" "dev" 1 "$root" "$pm" run dev
fi fi
fi fi
if [ -f "$root/pyproject.toml" ] && have uv; then if [ -f "$root/pyproject.toml" ] && have uv; then
quick "$(icon_for uv)" "uv sync" "sync,install" "$root" uv sync quick "$(icon_for uv)" "uv sync" "sync,install" 0 "$root" uv sync
quick "$(icon_for uv)" "uv run python" "py,repl" "$root" uv run python quick "$(icon_for uv)" "uv run python" "py,repl" 0 "$root" uv run python
fi fi
if have make && { [ -f "$root/Makefile" ] || [ -f "$root/makefile" ] || [ -f "$root/GNUmakefile" ]; }; then if have make && { [ -f "$root/Makefile" ] || [ -f "$root/makefile" ] || [ -f "$root/GNUmakefile" ]; }; then
quick "$(icon_for make)" "make" "m,build" "$root" make quick "$(icon_for make)" "make" "m,build" 0 "$root" make
fi
[ -f "$root/go.mod" ] && have go && quick "$(icon_for go)" "go run ." "run" 0 "$root" go run .
[ -f "$root/Cargo.toml" ] && have cargo && quick "$(icon_for cargo)" "cargo run" "run" 0 "$root" cargo run
if have docker; then
for f in compose.yaml compose.yml docker-compose.yaml docker-compose.yml; do
[ -f "$root/$f" ] || continue
quick '󰡨' "docker compose up" "up,compose" 1 "$root" docker compose up
break
done
fi fi
[ -f "$root/go.mod" ] && have go && quick "$(icon_for go)" "go run ." "run" "$root" go run .
[ -f "$root/Cargo.toml" ] && have cargo && quick "$(icon_for cargo)" "cargo run" "run" "$root" cargo run
# ── Every task pj finds (npm/bun/pnpm scripts, make, just, mise, devbox, # ── Every task pj finds (npm/bun/pnpm scripts, make, just, mise, devbox,
# pyproject task tables and console scripts) ───────────────────────────── # pyproject task tables and console scripts) ─────────────────────────────
while IFS=$'\t' read -r display kind target troot runner _file; do while IFS=$'\t' read -r display kind target troot runner _file; do
[ -n "$kind" ] || continue [ -n "$kind" ] || continue
tag=${display%% *} tag=${display%% *}
desc="$runner $target" desc="$runner $target" name=""
[ "$troot" != "$root" ] && desc+=" (${troot#"$root"/})" [ "$troot" != "$root" ] && desc+=" (${troot#"$root"/})"
long_running "$target" && { name=$(slug "$runner $target"); desc+=" · background"; }
item "Tasks: $tag" "$(icon_for "$tag")" "$target" "$desc" "" \ item "Tasks: $tag" "$(icon_for "$tag")" "$target" "$desc" "" \
"$(split_action "$troot" "$self" --run "$kind" "$target" "$troot" "$runner")" "$(task_action "$troot" "$name" --run "$kind" "$target" "$troot" "$runner")"
done < <(PJ_QUIET=1 pj --list 2>/dev/null) done < <(PJ_QUIET=1 pj --list 2>/dev/null)
if [ ${#items[@]} -eq 0 ]; then if [ ${#items[@]} -eq 0 ]; then
item Tasks '' "No tasks found" "in ${cwd/#$HOME/~}" "" "display-message 'pj: no task sources here'" item Tasks '' "No tasks found" "in ${cwd/#$HOME/\~}" "" "tmux display-message 'pj: no task sources here'"
fi fi
printf '%s\n' "${items[@]}" | jq -s . printf '%s\n' "${items[@]}" | jq -s .
+5
View File
@@ -117,3 +117,8 @@ command -v atuin >/dev/null 2>&1 && eval "$(atuin init zsh)"
# >>> termium >>> # >>> termium >>>
case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac case ":$PATH:" in *:'/home/prad/.local/bin':*) ;; *) export PATH='/home/prad/.local/bin':"$PATH" ;; esac
# <<< termium <<< # <<< termium <<<
# Added by LM Studio CLI (lms)
export PATH="$PATH:/home/prad/.lmstudio/bin"
# End of LM Studio CLI section
+7 -3
View File
@@ -14,11 +14,15 @@ command = [
{ name = "Repos", run = ''' { name = "Repos", run = '''
root=$(ghq root) root=$(ghq root)
{ {
ghq list | awk -v r="$root" '{ print $0 "\t" r "/" $0 }' # Checkouts living outside ghq root, listed first so they win over a ghq
# chezmoi's source is a github.com/prdlk/dotfiles checkout outside ghq root. # clone of the same repo: chezmoi's source (github.com/prdlk/dotfiles) and
# the nvim config (github.com/prdlk/nvim).
c=$HOME/.local/share/chezmoi c=$HOME/.local/share/chezmoi
[ -d "$c/.git" ] && printf 'github.com/prdlk/dotfiles\t%s\n' "$c" [ -d "$c/.git" ] && printf 'github.com/prdlk/dotfiles\t%s\n' "$c"
} | mtime-sort --repo n=$HOME/.config/nvim
[ -d "$n/.git" ] && printf 'github.com/prdlk/nvim\t%s\n' "$n"
ghq list | awk -v r="$root" '{ print $0 "\t" r "/" $0 }'
} | awk -F '\t' '!seen[$1]++' | mtime-sort --repo
''' }, ''' },
{ name = "Vaults", run = ''' { name = "Vaults", run = '''
v=$HOME/Documents/Obsidian v=$HOME/Documents/Obsidian