.\" SPDX-License-Identifier: GPL-3.0-or-later .Dd September 14, 2026 .Dt KRINO 1 .Os .Sh NAME .Nm krino .Nd sort files in a directory by rules .Sh SYNOPSIS .Nm .Op Fl y | Fl n .Op Fl v .Op Fl -json .Op Fl c Ar file .Op Fl P .Op Fl -no-color .Op Fl -min-age Ar duration .Op Ar name ... .Pp .Nm .Cm init .Pp .Nm .Cm new .Ar name path .Pp .Nm .Cm check .Op Ar name ... .Pp .Nm .Cm explain .Ar file .Pp .Nm .Cm log .Op Fl n Ar count .Pp .Nm .Cm undo .Op Ar run .Sh DESCRIPTION .Nm sorts the files in one or more configured directories according to rules kept in .Xr krino.conf 5 . For each directory in turn it scans the files, evaluates every rule against every file, and builds a plan: the chain of actions .Pq Ic copy , Ic move , Ic rename , Ic delete that the file's matching rules add up to. .Pp Every rule that matches a file adds its actions to that file's chain, in the order the rules are written; .Sy (stop) on a matching rule ends the chain for that file, so a later rule is never even evaluated against it. A rule with no actions besides .Sy (stop) is an exclusion. .Pp Every test sees the file as it was when the directory was scanned: name, path, size, modification time and content. Actions already added to a file's chain by an earlier rule never change what a later rule's tests see, so the whole plan for a directory can be computed, shown, and approved before anything is touched. .Pp With no .Fl y or .Fl n , .Nm prints the plan and asks how to proceed .Pq Sx REVIEW . .Fl y applies the plan without asking; .Fl n prints it and changes nothing. If standard input is not a terminal and neither is given, .Nm refuses rather than guess. .Sh OPTIONS .Bl -tag -width Ds .It Fl y Apply the plan without asking. Cannot be combined with .Fl n . .It Fl n Dry run: print the plan and exit without changing anything. .It Fl v Also list files that were skipped as unmatched, ignored or busy, and show the full reason a test matched or not. .It Fl -json With .Fl n , print the plan as JSON instead of the text blocks. Refused unless .Fl n is also given. The document's own .Ic note field says its shape is unstable before krino 1.0; a script that reads it should expect it to change before then. .It Fl c Ar file Use .Ar file in place of .Pa $XDG_CONFIG_HOME/krino/krino.conf . .It Fl -no-color Never colour the output, as when .Ev NO_COLOR is set. Without it, .Nm colours its output only when writing to a terminal, using the terminal's own 16-colour palette: directory headers bold; .Ic copy , .Ic move and .Ic rename green; .Ic trash yellow; .Sy DELETE permanently and a refused undo step bold red; skipped steps and match reasons faint; rule names blue; warnings yellow; the applied count green and a failed count red; prompt keys bold; the choice echoed in review red; and .Ic krino log Ns 's .Dq (undone) faint. .It Fl P , Fl -no-pager Print the plan straight out, never through .Ev PAGER . Without it, a plan taller than the terminal is shown through the pager. .It Fl -min-age Ar duration For this run only, skip files modified less than .Ar duration ago in every directory, in place of each directory's .Ic min-age .Pq Xr krino.conf 5 . .Ar duration is .Sy 0 , or an integer with one of .Ql s m h d w ; .Fl -min-age Cm 0 considers even a file written a moment ago, and a file whose modification time is ahead of the clock. Also honoured by .Ic explain ; every other command refuses it. .It Fl h , Fl -help Print usage and exit. .It Fl -version Print .Dq krino Ar version and exit. .El .Sh SUBCOMMANDS .Bl -tag -width Ds .It Ic init Create the config directory with a commented .Pa krino.conf and .Pa template.conf . Refuses if .Pa krino.conf already exists, and leaves an existing .Pa template.conf alone. .It Ic new Ar name path Copy .Pa template.conf to .Pa dirs/ Ns Ar name Ns Pa .conf , fill in .Ar path , and append .Ar name to .Ic include in .Pa krino.conf , keeping its comments. .It Ic check Op Ar name ... Validate the configuration, list each included directory's exclusions and rules, and list which content-extraction tools .Pq Xr krino.conf 5 , Sx CONTENT EXTRACTION are available. With no .Ar name , checks every included directory. .It Ic explain Ar file Evaluate every exclusion and rule of .Ar file Ns 's directory against it and show each test's result, so a rule that should match but does not .Pq or the reverse can be diagnosed test by test. See .Sx KNOWN LIMITATIONS . .It Ic log Op Fl n Ar count List the most recent runs, newest first .Pq Ar count No defaults to 10 : the run id, its start time, the directories it touched, and what it did. A run that a later .Ic undo has reversed at least one file of is marked .Pq undone . .It Ic undo Op Ar run Reverse .Ar run . With no .Ar run , reverse the most recent run; if that run is itself an undo, continue it: the run it undid is planned again, offering only the reversals it did not complete. An older run is undone by naming it. See .Sx UNDO . .El .Sh REVIEW With neither .Fl y nor .Fl n , after showing one directory's plan .Nm asks: .Bd -literal -offset indent [a] apply all [c] choose per file [s] skip this directory [q] quit .Ed .Pp .Ic a applies every step of every chain shown. .Ic s applies nothing in this directory and moves on to the next one. .Ic q stops .Nm entirely; directories already applied earlier in this run stay applied and can be reversed with .Ic undo . .Pp .Ic c asks about each file in turn, showing the same block the plan shows for it: each step, then the rule and the reason it matched: .Bd -literal -offset indent [2/41] fv_123.pdf copy \(-> ~/backup/invoices/2026/ rule backup because content "invoice" [y] yes [n] no [a] yes to this and all remaining [t] trash [d] delete permanently [w] write, apply chosen so far [q] quit, apply nothing .Ed .Pp Approval is per file: a file's whole chain runs, or none of it. .Ic t and .Ic d set aside what the rules planned for the file and approve one step on the file itself instead: to the Trash, or deleted permanently, logged under the rule name .Sy (review) . .Ic d first asks .Dq delete Ar name No permanently? [y/N] ; any key but .Ic y deletes nothing and asks about the file again. A permanent delete cannot be undone. Each choice is confirmed on its own line under the file, in red: .Dq \(-> yes , .Dq \(-> no , .Dq \(-> trash , and so on. Enter is ignored. .Pp .Ic w applies what was decided so far and stops .Nm , without going on to any later directory, so a long review can be done over several sessions. Files answered .Ic n are logged as declined; files never reached are not logged, are counted as .Dq not reviewed , and are asked about again next time, as are declined files. .Ic q here aborts the review for this directory entirely, discarding even a file already marked .Ic yes , and folds into the top-level .Ic q above. .Sh UNDO .Ic krino undo reverses a run's steps, last step first within each file, after building and showing a plan of its own .Pq shown and approved the same way as a sort plan; Fl y No and Fl n No apply . One undo plan covers every directory the run touched, so its menu has no per-directory skip; .Ic s and .Ic q both apply nothing: .Bd -literal -offset indent [a] apply all [c] choose per file [s] skip [q] quit .Ed .Pp Its per-file prompt is the one in .Sx REVIEW without .Ic t and .Ic d , and behaves the same way: choices are echoed, .Ic w applies what was decided and leaves the files it did not reach out of the run, counted as not reviewed. .Pp An undo run cannot itself be undone: naming it to .Ic undo is refused. Reversals an earlier undo of the same run completed are never offered again, so an undo that stopped part way is finished by undoing the run once more. .Pp A step's reversal is refused when the world has moved on since the step ran .Pq its target is gone or has changed, or the original path is occupied again ; a refused file has nothing of its chain reversed, so no file is left half undone. An exception is a created directory found non-empty at its own turn: that alone does not refuse the rest of the file's reversal, since it means another file still lives there, not that something unexpected changed underfoot. Every created directory still empty once every file in the run has had its turn is then removed, deepest path first, so an undo does not leave the empty directories it made behind. .Pp A permanent delete, .Sy (delete permanent) , is never undoable; it is shown in the plan with that reason and nothing is attempted for it. .Pp .Ic krino log marks a run .Pq undone once its undo run has reversed at least one file's chain \(em not that every file in it was restored: a run left partly reversed, because some files were declined during the undo's own review, is still shown as .Pq undone in full. .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev XDG_CONFIG_HOME Base of the configuration directory .Pq Pa $XDG_CONFIG_HOME/krino ; default .Pa ~/.config . .It Ev XDG_STATE_HOME Base of the log and the per-directory lock files .Pq Pa $XDG_STATE_HOME/krino ; default .Pa ~/.local/state . .It Ev XDG_DATA_HOME Base of the trash a .Sy (delete) step uses .Pq Pa $XDG_DATA_HOME/Trash ; default .Pa ~/.local/share . .It Ev XDG_CACHE_HOME Base of the keyword cache .Pq Pa $XDG_CACHE_HOME/krino ; default .Pa ~/.cache . .It Ev PAGER Used to show a plan taller than the terminal; default .Dq less -FRX . Never used for .Fl -json output. .It Ev NO_COLOR When set, disables colour, as .Fl -no-color does. .El .Sh FILES .Bl -tag -width Ds .It Pa $XDG_CONFIG_HOME/krino/krino.conf The main configuration file: which directories run, defaults, the log path. .It Pa $XDG_CONFIG_HOME/krino/template.conf Copied by .Ic krino new for each new directory. .It Pa $XDG_CONFIG_HOME/krino/dirs/ Ns Ar name Ns Pa .conf One file per configured directory. .It Pa $XDG_STATE_HOME/krino/krino.log The append-only log every run writes to. .It Pa $XDG_STATE_HOME/krino/ Ns Ar name Ns Pa .lock Held for the duration of a run against directory .Ar name , so a second .Nm against the same directory waits, or fails immediately with .Fl y . .It Pa $XDG_DATA_HOME/Trash The freedesktop.org trash a .Sy (delete) step moves files into. .It Pa $XDG_CACHE_HOME/krino/ Ns Ar name Ns Pa .cache Which content keywords each extracted file of directory .Ar name contains, so an unchanged file is not extracted again; see .Xr krino.conf 5 , Sx CONTENT EXTRACTION . Safe to delete at any time. .El .Sh EXIT STATUS .Bl -tag -width Ds .It 0 Success, including a run that found nothing to do, or one whose plan was entirely declined at review. .It 1 A step failed, a directory's lock was already held by another .Nm , or a configured directory could not be read. .It 2 A usage mistake or a configuration error. Flags must come before any directory name on the command line: a flag found after a directory name is a usage error rather than a guess. .It 130 Interrupted .Pq Ic Ctrl-C or terminated; the current step, if any, is finished and logged first. .El .Sh KNOWN LIMITATIONS .Ic krino explain evaluates every test of every rule's condition against the file, without the cost-ordered short-circuiting a real run uses within an .Sy and/or . It can therefore run a content-extraction tool, and report a .Dq content unreadable warning, for a test that a real run would never reach because an earlier, cheaper test in the same condition already decided the rule did not match. .Ic explain is a full trace of every test, not a cost-faithful preview of what .Fl n would actually spend time on. .Sh SEE ALSO .Xr krino.conf 5 .Pp .Pa docs/sexp-primer.md in the source repository .Pq installed at Pa $PREFIX/share/doc/krino/sexp-primer.md is a short tutorial on the configuration syntax.