A TUI tool for managing your chirashi no ura — disposable directories where you scatter and scribble throwaway stuff (Rust + ratatui).
🌐 This English README is machine-translated. The authoritative version is 日本語版 / README.ja.md.
It handles not only note files but also "directories where you try running something," letting you list, create, and delete them in one place — and open a shell right inside a selected directory to experiment or run agents. Editing is delegated to $EDITOR (no built-in editor).
chirashi no ura (チラシの裏) literally means "the back of a flyer" — in Japan, the throwaway surface you scribble worthless little things on. The name chira comes from 散らす (chirasu, "to scatter") → チラシ (chirashi, a flyer).
The Browse screen right after launch looks like this (list on the left, preview or directory contents of the selection on the right, key hints on the bottom line). The middle header segment (chira) is the path relative to CHIRA_DIR root and expands like chira/foo once you descend into a subdirectory. The right-pane tree starts at the children of the selected directory (per src/scratch.rs::tree):
chira chira 4 items
┌─ List ──────────────────────────┐┌─ Directory contents ──────────────────┐
│› 06/22 00:59 try-agent/ ││ ├── README.md │
│ 06/21 22:10 sandbox/ ││ ├── run.sh │
│ 06/21 18:42 memo.md ││ └── notes/ │
│ 06/20 14:10 TODO.md ││ ├── 2026-06-21.md │
│ ││ └── 2026-06-22.md │
│ ││ │
│ ││ │
└─────────────────────────────────┘└───────────────────────────────────────┘
j/k:move l:open h:parent s:shell n:new /:filter ?:help q:quit
cargo install --locked chira--locked makes the build use the published Cargo.lock, so you get the exact dependency versions that were tested.
chira can move your calling shell's working directory to the directory you ended up in when you quit (the lf / nnn approach), or to the directory chira mkdir <name> just created. A child process cannot change its parent shell's cwd directly, so chira writes the target directory via --cd-file and a shell function does the cd. Without this wrapper, running the bare chira binary cannot change your shell's directory.
Add to your shell startup file (~/.zshrc for zsh, ~/.bashrc for bash):
chira() {
local tmp; tmp="$(mktemp)" || return
command chira --cd-file "$tmp" "$@"
# In zsh, $status is a special parameter (alias for $?) that cannot be declared with `local`, so use a different name
local rc=$?
local dir; dir="$(cat "$tmp")"
rm -f "$tmp"
[ -n "$dir" ] && [ -d "$dir" ] && [ "$dir" != "$PWD" ] && cd "$dir"
return $rc
}Or ~/.config/fish/functions/chira.fish (fish):
function chira
set -l tmp (mktemp); or return
command chira --cd-file $tmp $argv
set -l cmd_status $status
set -l dir (cat $tmp)
rm -f $tmp
test -n "$dir"; and test -d "$dir"; and test "$dir" != "$PWD"; and cd "$dir"
return $cmd_status
endThe trailing return $rc (or return $cmd_status in fish) propagates the exit code of CLI subcommands like chira gc (1 when errors > 0, 2 on argument errors) straight through as the wrapper's return value. Even when cd is performed via the TUI, the wrapper returns chira's own exit code, not the success/failure of cd.
Now: launch chira → descend into a directory → quit with q, and your shell moves there. chira mkdir <name> reuses the same wrapper and immediately cds you into the newly created directory. Right after, the shell's standard cd - takes you back to where you were (because cd sets OLDPWD).
The location is resolved in this order: $CHIRA_DIR → $XDG_DATA_HOME/chira → ~/.local/share/chira (XDG-style even on macOS; Apple's Application Support is not used, so it's easy to handle from the terminal). The contents are plain files and directories, so they work directly with external editors, grep, and dotfiles sync.
CHIRA_DIR=~/scratch chira # use a different locationFor persistent settings without touching your shell startup files, chira reads a TOML config file. The path is resolved in this order: $CHIRA_CONFIG (a direct path) → $XDG_CONFIG_HOME/chira/config.toml → ~/.config/chira/config.toml. A missing or empty file is treated as "unset" (no warning); a broken file prints a warning to stderr and starts with defaults.
# ~/.config/chira/config.toml
dir = "~/scratch" # storage location (leading ~ is expanded to $HOME)
editor = "nvim" # external editor (arguments allowed, e.g. "code --wait")
shell = "/bin/zsh" # shell opened with `s` (arguments allowed, e.g. "zsh -l")Each value falls back independently when omitted. Resolution priority (high → low) is environment variable → config file → built-in default, so existing env-based usage keeps working:
dir:$CHIRA_DIR→dir→$XDG_DATA_HOME/chira→~/.local/share/chiraeditor:$EDITOR→editor→vishell:$SHELL→shell→/bin/sh
CHIRA_DIR=/tmp/other chira # env wins over config's dirchira new / chira mkdir without an argument auto-generate a name from a chrono strftime template. The defaults are scratch-%Y%m%d-%H%M%S.md (file) and scratch-%Y%m%d-%H%M%S (directory). To customize:
[new]
name_template = "memo-%Y-%m-%d_%H%M%S.md" # used when `chira new` is called without a name
dir_template = "memo-%Y-%m-%d_%H%M%S" # used when `chira mkdir` is called without a name- Any
chronoformat specifier ( https://docs.rs/chrono/latest/chrono/format/strftime/ ) is accepted; the result is the entry name on disk. - Leading/trailing whitespace is trimmed before use; a whitespace-only value is treated as unset.
- Unset or empty values fall back to the built-in defaults above.
- The TUI placeholders for
n(new file) andN(new directory) use the same templates, so CLI and TUI stay in sync. - If a template renders to a name that fails
chira's safety check (empty, contains/, or starts with.), or contains achronospecifier that cannot be parsed (e.g.%Q), a warning is printed at startup and the default is used instead.
Inside the TUI press , to open the configuration screen. It lists every option with its current value and source — (env: CHIRA_DIR) / (config) / (default) — plus the resolution order and the absolute path the changes will be saved to. Enter edits the highlighted entry (Space toggles booleans), s writes the changes back to the same config.toml while preserving formatting and comments, and Esc returns to the file list. Items currently overridden by an env var are marked ⚠ env override — the file is still updated, but the env var continues to take precedence on the next launch. Values written by the TUI take effect on the next start (the current session keeps the snapshot loaded at boot).
To bootstrap a new directory with a command of your choice (rsync a skeleton, git clone, scaffold via cookiecutter, anything), define [actions.<name>] entries in config.toml. Pressing t opens a picker, asks for a directory name, shows the resolved command in a confirm screen (trust gate — config-derived shell runs as you), then on y creates the directory and runs the command via sh -c inside it.
# Bootstrap an isolated dev shell
[actions.nix-sandbox]
description = "Nix flake + direnv sandbox"
run = "rsync -a ~/.config/chira/skel/nix-sandbox/ ./ && direnv allow"
# Clone a sandbox repo
[actions.clone-sandbox]
description = "Clone my sandbox repo"
run = "git clone --depth 1 git@github.com:me/sandbox.git ."
# Delegate interpolation to a dedicated generator
[actions.from-copier]
run = "copier copy --trust ~/.config/chira/templates/app \"$CHIRA_TARGET\""Optional: make N (plain new directory) auto-run a specific action. Default-off; with this set, N becomes the same flow as t → <name>. Place default_action before any [actions.*] table so TOML parses it as a root-level key (otherwise it becomes a key inside the preceding [actions.<name>] table and is silently ignored):
default_action = "nix-sandbox"
[actions.nix-sandbox]
# ...descriptionis shown in the picker (optional).runis required; entries with missing/emptyrunare silently skipped.- The
runcommand is executed via/bin/sh -cso pipelines (&&,|),~expansion, and$VARwork as usual. - chira itself does not interpolate file contents or filenames. If you need templating, call a dedicated tool from
run(cookiecutter --no-input,copier copy --trust,envsubst,sed, etc.). - The new directory is the cwd.
runalso receives env vars:CHIRA_TARGET— absolute path of the new directoryCHIRA_TARGET_NAME— directory nameCHIRA_ROOT— scratch root ($CHIRA_DIR)
- On a non-zero exit, the directory is kept (no auto-rollback — diagnostic state may be useful) and
.chira/bootstrap-failedis written inside it. The list shows[!]in front of those directories so you can spot the half-provisioned ones. To retry, delete the directory withdand re-run the action (chira always creates a fresh directory and refuses an existing name); alternatively, remove.chira/bootstrap-failedmanually to clear the marker without re-running. default_action = "<name>"makes the plainNkey go through the same picker-less confirm + run flow. With it unset (default),Nkeeps creating an empty directory. An unknown name silently falls back to the plain-Nbehavior.
UI strings (help overlay, status messages, prompts) follow this resolution order:
CHIRA_LANG— explicit override (case-insensitive). Accepted values:ja/ja_jp/japanese→ Japanese,en/en_us/english→ English. Any other value falls through to the locale check- POSIX locale:
LC_ALL→LC_MESSAGES→LANG(values starting withjaselect Japanese; everything else, includingC/POSIX, selects English) - Default: English
CHIRA_LANG=ja chira # force Japanese UI
CHIRA_LANG=en chira # force English UI| Key | Action |
|---|---|
j/↓, k/↑ |
move cursor |
g/G |
top / bottom |
Enter / l / → |
open (file → $EDITOR / directory → descend into it) |
h / ← / Backspace |
go to parent directory |
e |
open the selected file in $EDITOR (files only) |
s |
open $SHELL in the selected directory (or current if none) — for experiments / running agents |
n |
new file (enter a name → open in $EDITOR) |
N |
new directory (with default_action: same as t + that action) |
t |
new directory from an action — pick → name → confirm command → run ([actions.*]) |
r |
rename |
d |
delete (with confirmation; directories are removed recursively) |
/ |
filter by name |
, |
open the configuration screen (edit config.toml in place) |
? |
show help on screen (any key closes it) |
q |
quit |
Like vim, h/j/k/l navigate (h = parent, l = open), and arrow keys work too. Help is ? (same as ranger / nnn). Selecting a directory shows its contents in the right pane as a tree-style view (depth 4, up to 100 lines).
Quitting $EDITOR (default vi) or the shell opened with s (default /bin/sh) returns you to the TUI, and any files created in the meantime show up in the list automatically. Create / rename / delete are reflected on disk immediately.
Running chira with no arguments launches the TUI. Pass a subcommand to run a one-shot CLI operation instead — useful for piping (chira ls | fzf), scripts, or quick cd integration without spinning up the TUI.
| Subcommand | TUI equivalent | Notes |
|---|---|---|
chira ls [<path>] |
(list view) | One name per line; -l prints <mtime>\t<size>\t<name> |
chira tree [<path>] |
(right pane) | Tree view (depth 4, up to 100 lines) |
chira new [<name>] |
n |
Create a file and open $EDITOR; --no-edit skips the editor. When <name> is omitted, the name is generated from [new] name_template (default scratch-%Y%m%d-%H%M%S.md) |
chira mkdir [<name>] |
N |
Create a directory. When <name> is omitted, the name is generated from [new] dir_template (default scratch-%Y%m%d-%H%M%S) |
chira edit <name> |
e |
Open <name> in $EDITOR |
chira shell [<dir>] |
s |
Open $SHELL in <dir> (or in CHIRA_DIR if omitted) |
chira rm <name> |
d |
Delete; -r is required for directories, -f skips confirmation |
chira mv <old> <new> |
r |
Rename |
chira path [<name>] |
— | Print the full path of an entry (or CHIRA_DIR if omitted) |
chira find <query> [<path>] |
/ |
List entries whose name matches the substring (ls-style output) |
chira gc [--ttl <dur>] [--archive-dir <path>] [--dry-run] |
— | Move entries whose mtime exceeds TTL to the archive dir (see below) |
Output is biased toward machine-readability: ls / find print one name per line, color and the trailing / for directories appear only when stdout is a TTY. Errors go to stderr; missing entries exit 1, argument errors exit 2. Destructive operations (rm / mv) verify that the target is under CHIRA_DIR (canonicalized; .. and symlink escapes are rejected). On a symlink, rm removes the symlink itself (unix rm semantics), not the target. In non-interactive (non-TTY stdin) contexts, rm requires -f; without it the prompt auto-cancels with exit 1. rm / mv refuse to operate on the scratch root itself (. / empty name) to prevent accidentally destroying the whole CHIRA_DIR.
new and mkdir accept a single basename and always create at CHIRA_DIR root (slashes in the name are rejected). The other commands (ls, tree, edit, shell, rm, mv, path, find) accept root-relative paths and can target nested entries (e.g. chira edit ws/note.md). To create inside a subdirectory, open a shell in it first (chira shell ws).
chira path enables shell-side cd without launching the TUI:
cd "$(chira path)" # cd into CHIRA_DIR
cd "$(chira path my-experiment)" # cd into a specific entrychira treats entries as throwaways, but stale ones pile up unless you delete them by hand. chira gc sweeps entries whose mtime is older than a TTL and moves them under <CHIRA_DIR>/.archive/ (a hidden directory, so it disappears from the main listing). The archived files stay as plain files/directories, so find / grep still work on them.
chira gc --ttl 30d # archive entries older than 30 days
chira gc --ttl 12h --dry-run # preview without moving
chira gc --archive-dir ~/old # move to a custom locationTime units: s / m / h / d / w (no unit defaults to seconds). The TTL is required — chira gc exits with an error if neither --ttl nor [archive] ttl_days is set, so an unconfigured invocation never erases anything by surprise.
The mtime is read from the entry itself (symlink_metadata, so intact symlinks are aged by the link itself, not the target). atime is not used because relatime / noatime mounts do not update it. Dangling symlinks (target missing) are treated as "mtime cannot be read" — they are tracked under errors, not aged by the link.
Even past the TTL, the following are kept (counted as kept in the summary):
- The
.archive/directory itself (so re-sweeps stay safe) - Directories that contain a
.keepmarker file (lf / nnn convention) - Names that match any pattern in
[archive] keep(see below)
Entries whose mtime cannot be read (broken symlinks etc.) are tracked separately as errors — they are skipped with a warning on stderr and counted in errors, not kept. The summary line shows archived / kept / errors as independent categories, and a non-zero errors count exits with code 1 so cron can detect the condition.
[archive]
# TTL in days (0 / unset means archive is off; CLI --ttl can still drive a one-shot run)
ttl_days = 30
# Archive destination. ~ is expanded. Defaults to <CHIRA_DIR>/.archive
dir = "~/scratch-archive"
# Sweep on TUI startup. Default false (a surprise sweep on every run is too aggressive)
on_startup = false
# Names matching any of these globs are kept. A trailing `/` restricts the match to directories
keep = ["pinned-*", "longterm/"]Globs support * (any run) and ? (any single character). A trailing / makes the pattern match directories only.
Name collisions in the archive directory get a .<unix_ts> suffix appended (e.g. old.md.1742278300); a second collision at the same second appends _1, _2, etc. Cross-filesystem moves fail with the underlying EXDEV error reported to stderr — point dir at a location on the same filesystem as CHIRA_DIR if you need that case to succeed.
cargo run # run from source
cargo build --release # build the single binaryMIT