summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md214
1 files changed, 214 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4b7d4c3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,214 @@
+# krino
+
+krino sorts the files in one or more directories by rules: conditions on a
+file's type, name, path, size, age, text content or duplicate status,
+combined with `and` / `or` / `not`, that trigger actions — copy, move,
+rename, delete (to the Trash by default), chained in the order written. It
+shows the plan before touching anything, lets you approve all of it or file
+by file, logs every step, and can undo a run. Version 0.0.1 is the engine
+and this command-line interface: per-directory rule files, placeholders in
+destinations and new names, and builds for Linux, FreeBSD and OpenBSD. It is
+not a GUI (that comes later), a watch mode, OCR, EXIF dates, macOS or
+Windows support, a content cache, a shell-command action, or a way to look
+inside archives.
+
+## Install
+
+```
+make && make install
+```
+
+`make` builds `./krino`; Go fetches the module dependencies itself. `make
+install` puts the binary in `$PREFIX/bin` (default `~/.local/bin`), the man
+pages in `$PREFIX/share/man`, and the examples and `docs/sexp-primer.md` in
+`$PREFIX/share/doc/krino`.
+
+Some `(content ...)` tests need external extractors — `pdftotext` for PDF,
+`antiword`/`catdoc` for legacy Word, `xls2csv`/`catppt` for legacy Excel and
+PowerPoint. `make build` reports which of these are missing; install them
+with:
+
+```
+make deps
+```
+
+`make deps` is the only target that asks for privileges — it calls the
+system package manager (`apt-get`, `pkg`, or `pkg_add`, depending on the
+OS). Plain `make` never does.
+
+## The 60-second quickstart
+
+Everything below happens in `/tmp/krino-demo` and a scratch config under
+`/tmp/krino-xdg`, so it never touches a real directory or your real krino
+config. Copy-paste it as written and your output should match what's pasted
+here, except for the run id and timestamps, which are different every time.
+
+Create a handful of files of different types:
+
+```
+mkdir -p /tmp/krino-demo
+cd /tmp/krino-demo
+for f in acme-invoice.pdf photo.jpg podcast.mp3 archive.zip app.deb notes.txt; do
+ printf 'demo file: %s\n' "$f" >"$f"
+done
+```
+
+Point krino at a scratch config for this walkthrough:
+
+```
+export XDG_CONFIG_HOME=/tmp/krino-xdg/config
+export XDG_STATE_HOME=/tmp/krino-xdg/state
+export XDG_DATA_HOME=/tmp/krino-xdg/data
+```
+
+Set it up:
+
+```
+$ krino init
+created /tmp/krino-xdg/config/krino/krino.conf
+created /tmp/krino-xdg/config/krino/template.conf
+next: krino new NAME PATH, for example: krino new downloads ~/Downloads
+$ krino new demo /tmp/krino-demo
+created /tmp/krino-xdg/config/krino/dirs/demo.conf and added "demo" to include
+edit its rules, then check them with: krino check demo
+```
+
+`krino new` copied the template to `dirs/demo.conf` and filled in the path.
+Replace its body with a small rule. The built-in `min-age` is 2 minutes
+(files skip as "busy" while they might still be downloading), which the
+demo files we just created are younger than, so this also turns it down to
+0 for this directory:
+
+```
+cat > "$XDG_CONFIG_HOME/krino/dirs/demo.conf" <<'EOF'
+;; -*- mode: lisp -*-
+;; vim: set ft=lisp :
+
+(path "/tmp/krino-demo")
+(min-age 0s) ; demo files are seconds old; built-in is 2m
+
+(rule "acme"
+ (when (name "acme"))
+ (move "Filed/Acme"))
+EOF
+```
+
+This one rule matches any file whose name contains "acme" and moves it into
+`Filed/Acme`. See what it would do, without changing anything:
+
+```
+$ krino -n demo
+krino: demo /tmp/krino-demo
+6 scanned · 1 to act on · 0 warnings · 0.00s
+
+ # file actions rule
+ 1 acme-invoice.pdf move → Filed/Acme/ acme name "acme"
+
+not acted on: 5 unmatched (-v lists them)
+```
+
+Without `-n` or `-y`, `krino demo` shows the same plan and then asks, one
+key at a time, no Enter needed:
+
+```
+[a] apply all [c] choose per file [s] skip this directory [q] quit
+```
+
+`c` asks about each file in turn:
+
+```
+ [y] yes [n] no [a] yes to this and all remaining [d] done, apply chosen so far [q] quit, apply nothing
+```
+
+Approval is per file: a file's whole chain runs, or none of it. A README
+can't paste a session it didn't actually run in a terminal, so here we
+apply directly with `-y`, which shows the same plan and applies it without
+asking:
+
+```
+$ krino -y demo
+krino: demo /tmp/krino-demo
+6 scanned · 1 to act on · 0 warnings · 0.00s
+
+ # file actions rule
+ 1 acme-invoice.pdf move → Filed/Acme/ acme name "acme"
+
+not acted on: 5 unmatched (-v lists them)
+1 applied · 0 failed · 0 declined
+```
+
+`acme-invoice.pdf` is now in `Filed/Acme/`. Every run is logged:
+
+```
+$ krino log
+20260913T005509-db14 2026-09-13 00:55 demo 1 moved
+```
+
+And every run can be undone — `undo` alone would show its own plan and ask
+the same way, with a menu of its own:
+
+```
+[a] apply all [c] choose per file [s] skip [q] quit
+```
+
+`-y` applies it directly:
+
+```
+$ krino undo -y
+krino: undo 20260913T005509-db14
+1 files · 1 to reverse · 0 refused
+
+ # file steps
+ 1 demo/acme-invoice.pdf undo-move → /tmp/krino-demo/acme-invoice.pdf
+ undo-mkdir /tmp/krino-demo/Filed/Acme
+ undo-mkdir /tmp/krino-demo/Filed
+1 applied · 0 failed · 0 declined
+```
+
+`/tmp/krino-demo` is back exactly as the first `printf` loop left it — that
+is what `krino undo` is for. Delete both scratch directories when you're
+done: `rm -rf /tmp/krino-demo /tmp/krino-xdg`.
+
+## Configuration
+
+Rules are s-expressions. `docs/sexp-primer.md` is a short tutorial on the
+syntax with no Lisp background assumed; `krino.conf(5)` (`man/krino.conf.5`
+in the source, installed as a man page) is the reference for every form,
+setting and condition. `examples/` has four worked rule files: by type, by
+content (invoices), by age (old installers), and renaming (screenshots).
+
+Rules live in `$XDG_CONFIG_HOME/krino` (default `~/.config/krino`) —
+**never in this repository**. A leak check guards that: it runs in `make ci`,
+and on every commit only after `make install-hooks`. It refuses staged files
+that contain your home directory's path or an email address, and it catches
+rule content only when a private pattern list is configured with
+`git config krino.leakpatterns FILE`.
+
+## Safety
+
+- `(delete)` moves a file to the freedesktop.org Trash by default, not
+ `unlink(2)` — recoverable, by hand or with `krino undo`. `(delete
+ permanent)` is the explicit opt-out, and undo can never reverse it.
+- Every step of every run is appended to `$XDG_STATE_HOME/krino/krino.log`
+ (default `~/.local/state/krino/krino.log`), tab-separated and
+ `grep`-able.
+- `krino undo` reverses the most recent run, or a specific one by id from
+ `krino log`. A reversal that the world has moved on since (the target is
+ gone, changed, or the original path is occupied again) is refused for
+ that file rather than guessed at.
+- `-n` prints the plan and changes nothing, ever — no config is written, no
+ file is touched, nothing is logged.
+
+## Status
+
+**0.0.1, local only.** The module path is `krino`; `go install
+krino@v0.0.1` only works once a remote is chosen and the imports are
+renamed to match (`docs/design.md`, §18). Until then, build from a clone.
+`krino -n --json`'s output shape is unstable before krino 1.0 — don't
+script against it yet.
+
+## License
+
+Everything in this repository — code, man pages, examples and the embedded
+templates — is GPL-3.0-or-later. Full text in `LICENSE`. Go files, shell
+scripts and man pages carry an SPDX identifier.