lf-toolbox
A set of useful command-line tools for enhancing productivity.
Features
- tree: Display a directory tree with optional depth/hidden skipping, plus a flexible batch file processor that imports your function from a sibling Python file.
- youtube: Download YouTube videos, audio, and subtitles, or display video metadata.
- clock: Full-screen seven-segment terminal clock with stopwatch and countdown.
- cheque: Convert HKD amounts (supports cents) to formal HK cheque wording in Traditional Chinese and English.
- compress: Compress PDF files with Ghostscript and videos to smaller MP4 files with FFmpeg.
- plot: Plot CSV data with pyqtgraph subplots.
- note: Create, organize, list, and open timestamped Markdown notes.
- coffee: On macOS, keep the Mac awake while a local Cursor agent is working, then let it sleep when that work is idle.
Installation
Install from PyPI (recommended):
pipx install lf-toolbox
Or via pip:
pip install lf-toolbox
Install from GitHub:
pip install "git+https://github.com/o0fung/toolbox.git"
For local development:
git clone https://github.com/o0fung/toolbox.git
cd toolbox
python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e ".[dev]"
Upgrade
Upgrade a pipx install:
pipx upgrade lf-toolbox
Upgrade a pip install:
pip install -U lf-toolbox
Upgrade from GitHub:
pip install -U "git+https://github.com/o0fung/toolbox.git"
If pip says it's already satisfied or the version hasn't changed, force a reinstall:
pip install -U --force-reinstall "git+https://github.com/o0fung/toolbox.git"
Publish to PyPI
See RELEASING.md for the Git tag -> GitHub Actions -> PyPI publish checklist.
Usage
Run the installed CLI:
lf --help
Or run via Python entry point in this repo:
python cli.py [COMMAND] [OPTIONS]
🚩 tree
Display a directory tree.
Usage:
python cli.py tree show PATH [--depth DEPTH] [--skip-hidden]
PATH: Root directory to display.--depth,-d: Maximum depth to display (default: unlimited).--skip-hidden,-s: Skip hidden files and directories.
Batch file processor
Import and execute a function from a Python module located in the same folder as the target file/folder. The function will be called for each discovered file.
Usage:
# Installed entrypoint
lf tree work PATH [-d DEPTH] [--skip-hidden] [-m MODULE] [-f FUNC]
# From the repo
python cli.py tree work PATH [-d DEPTH] [--skip-hidden] [-m MODULE] [-f FUNC]
Options:
PATH(required): A file or a directory. If a directory, items are processed up to the specified depth.-d, --depth(default: 1): Maximum depth when displaying/processing.-s, --skip-hidden: Skip hidden/special files (dot- or underscore-prefixed).-m, --module(default:_script): Python module name (without.py) in the same folder asPATHto import.-f, --func(default:_test): Function name to call inside the module. It should accept a singlefilepath: strargument. If the function returns a non-None string, it will be shown on that file’s line.
Behavior niceties:
- If the module file doesn’t exist, a template is copied from
tools/_script.pyto help you start quickly. - If the function doesn’t exist in the module, a stub will be appended automatically so you can fill it in.
Example stub and runs:
# In the same folder as your files, create _script.py with:
from pathlib import Path
def _test(filepath: str):
p = Path(filepath)
if p.suffix.lower() == ".txt":
new = p.with_name(p.stem + "_bak" + p.suffix)
p.rename(new)
return f"Renamed: {p.name} -> {new.name}"
# Rename .txt files in a folder (depth 1)
lf tree work ~/docs -m _script -f _test
# Show tree only
lf tree show ~/docs -d 2 -s
🚩 youtube
Download YouTube content, list formats, or show metadata (powered by yt-dlp).
Basic Usage:
python cli.py youtube URL [options]
If no download-related flags are given, metadata only is displayed.
Common Flags:
URL(positional): YouTube video URL.--video,-v: Download best video (mp4-preferred) with audio using smart fallback formats.--audio,-a: Download best audio and convert to mp3 (192 kbps) via ffmpeg.--subtitle,-s: Download English subtitles (manual + auto). Use with other modes or alone (metadata + subs).--list: List all available formats (no download) in a compact table.--fmt FORMAT_EXPR: Explicit yt-dlp format selector (e.g.251,137+251,bestvideo[height<=720]+bestaudio/best). Overrides-v/-alogic.--out DIR: Output directory (default:~/Desktop). Created if missing.
Format Fallback Logic (when using -v without --fmt):
bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]bestvideo*+bestaudio*/bestvideo+bestaudiobest
Each failed attempt logs a warning and moves to the next expression. On total failure you'll be prompted to run --list and choose a format via --fmt.
Examples:
# Show metadata only
python cli.py youtube https://www.youtube.com/watch?v=ID
# List formats
python cli.py youtube https://www.youtube.com/watch?v=ID --list
# Download best mp4 (auto fallback) to Desktop
python cli.py youtube https://www.youtube.com/watch?v=ID -v
# Download audio only as mp3 to a custom folder
python cli.py youtube https://www.youtube.com/watch?v=ID -a --out ~/Media/audio
# Download subtitles only (no media)
python cli.py youtube https://www.youtube.com/watch?v=ID -s
# Explicit format combine 1080p video (137) + opus audio (251)
python cli.py youtube https://www.youtube.com/watch?v=ID --fmt 137+251 --out ./downloads
# Constrain height (yt-dlp expression)
python cli.py youtube https://www.youtube.com/watch?v=ID --fmt "bestvideo[height<=720]+bestaudio/best[height<=720]"
Notes:
- Filenames are the video title; adjust or sanitize as needed manually for now.
- Subtitles default language: English (
--subtitleenables both manual + auto if available). - Use
--fmtfor full control; all yt-dlp format selectors are supported. - Output directory is echoed at start:
[info] Output directory: /path/... - When all format attempts fail you will see a hint to run
--list. - Video/audio flows may require system FFmpeg tools (
ffmpeg,ffprobe):- macOS:
brew install ffmpeg - Ubuntu/Debian:
sudo apt install ffmpeg - Windows:
winget install Gyan.FFmpeg
- macOS:
- Missing
ffmpeg/ffprobecan trigger an interactive install prompt (y/N) using an available package manager (e.g. brew/apt/dnf/yum/pacman/zypper/winget/choco). - If Python
yt-dlpmodule is missing, an interactive install prompt is also available.
🚩 clock
Full-screen digital clock rendered with block characters using Rich. Updates every second.
Usage:
# Clock (from the repo)
python cli.py clock [-c COLOR] [-s SIZE]
# Stopwatch (counts up)
python cli.py clock timer [-c COLOR] [-s SIZE]
# Countdown (flexible input)
# S -> seconds
# H M -> hours minutes
# H M S -> hours minutes seconds
# Non-digit separators like spaces, ':', ',', '/' are accepted.
python cli.py clock countdown 45 // 45 seconds
python cli.py clock countdown 1 10 // 1 minute 10 seconds
python cli.py clock countdown 1 0 1 // 1 hour and 1 second
python cli.py clock countdown 2:15:00 -s xlarge -c magenta // 2 hours 15 minutes
# Mixed option placement (supported)
python cli.py clock -s large countdown 1 10 -c magenta
python cli.py clock countdown 1 10 -c magenta -s large
- Press
Ctrl+Cto quit.
Options:
--color,-c: Rich color style for digits (e.g., cyan, magenta, "#00ff00").--size,-s: Size preset. One of:small,medium,large,xlarge,xxlarge,xxxlarge.
Notes:
- When running
clockwithout a subcommand, the live clock UI starts. - When running a subcommand (
timer,countdown), the clock UI callback does not run. - The countdown holds at
00:00:00until you pressCtrl+C. - For
clock, place parent options before the subcommand; options after the subcommand are treated as subcommand options.
🚩 cheque
Render HKD amounts (dollars + optional cents) as formal wording for Hong Kong cheques in both Traditional Chinese and English.
Usage:
# From the repo
python cli.py cheque AMOUNT
AMOUNT: Non-negative numeric amount with up to 2 decimal places. Examples:0,10,1234567,123.45,0.05.
Output format:
- Chinese:
- cents is zero:
中文:港幣<FINANCIAL_UPPERCASE>元正 - cents exists:
中文:港幣<FINANCIAL_UPPERCASE>元<角分>
- cents is zero:
- English:
English: Hong Kong Dollars <words> only
Rules implemented:
- Chinese uses financial uppercase numerals (壹貳叁肆伍陸柒捌玖零) with units: 仟佰拾 within each group and 萬/億/兆 across groups.
- Inserts a single
零where a unit gap is present (e.g., 1001 -> 壹仟零壹;1000001 -> 壹佰萬零壹)。 - Chinese cents are rendered with
角/分(e.g.,0.05->零伍分). - English follows British/HK style:
- Uses "and" within hundreds (e.g., one hundred and two).
- Uses "and" between the last group (<100) and a higher group (e.g., one thousand and ten).
- Hyphenates 21–99 (e.g., twenty-one).
- Cents are rendered in words (e.g.,
123.45->... and forty-five cents only).
Examples:
python cli.py cheque 0
中文:港幣零元正
English: Hong Kong Dollars Zero only
python cli.py cheque 1001
中文:港幣壹仟零壹元正
English: Hong Kong Dollars One thousand and one only
python cli.py cheque 1000001
中文:港幣壹佰萬零壹元正
English: Hong Kong Dollars One million and one only
python cli.py cheque 120034
中文:港幣壹拾貳萬零叁拾肆元正
English: Hong Kong Dollars One hundred and twenty thousand and thirty-four only
python cli.py cheque 123.45
中文:港幣壹佰貳拾叁元肆角伍分
English: Hong Kong Dollars One hundred and twenty-three and forty-five cents only
python cli.py cheque 0.05
中文:港幣零元零伍分
English: Hong Kong Dollars Zero and five cents only
Notes:
- Extend English scales beyond trillion by editing
tools/cheque.pyif needed.
🚩 compress
Compress files. PDFs use Ghostscript quality presets; videos use FFmpeg to produce smaller H.264/AAC MP4 files.
Usage:
# From the repo
python cli.py compress pdf INPUT.pdf [options]
python cli.py compress video INPUT.mov [options]
# Installed entrypoint
lf compress pdf INPUT.pdf [options]
lf compress video INPUT.mov [options]
PDF options:
INPUT.pdf(required): Source PDF file path.-o, --out PATH: Output PDF path. Default is<input_stem>_compressed.pdfin the same folder.-q, --quality: Compression profile. One of:screen,ebook(default),printer,prepress,default.
Video options:
INPUT.mov/ video path (required): Source video file path.-o, --out PATH: Output MP4 path. Default is<input_stem>_compressed.mp4in the same folder.-w, --width: Output width in pixels. Default:1280; height is calculated automatically.--crf: x264 quality factor from0to51. Default:28; higher means smaller file and lower quality.-p, --preset: x264 preset. Default:slow.--overwrite: Allow replacing an existing output file.
Examples:
# Default PDF profile (ebook)
lf compress pdf ~/Desktop/report.pdf
# Stronger compression for on-screen reading
lf compress pdf ~/Desktop/report.pdf -q screen
# Keep higher print quality and choose output path
lf compress pdf ~/Desktop/report.pdf -q printer -o ~/Desktop/report_print.pdf
# Compress a large MOV to a smaller MP4
lf compress video ~/Desktop/input.mov
# Equivalent to: ffmpeg -i input.mov -vf scale=1280:-2 -c:v libx264 -crf 28 -preset slow -c:a aac output.mp4
lf compress video ~/Desktop/input.mov -o ~/Desktop/output.mp4
# Smaller video file with lower quality
lf compress video ~/Desktop/input.mov --crf 32
# Option after positional (equivalent)
lf compress pdf ~/Desktop/report.pdf -q printer
lf compress video ~/Desktop/input.mov --crf 30
Notes:
- Requires Ghostscript (
gs) installed and available on PATH. - macOS install:
brew install ghostscript - Missing
gscan trigger an interactive install prompt (y/N) using an available package manager (e.g. brew/apt/dnf/yum/pacman/zypper/winget/choco). - Video compression requires FFmpeg (
ffmpeg) installed and available on PATH. - macOS install:
brew install ffmpeg - Missing
ffmpegcan trigger the same interactive install prompt flow. - Output path must be different from input path.
- Compression ratio depends on source content (embedded images/fonts/compression).
- Video output is always MP4. The scaler uses
-2for height so FFmpeg keeps the aspect ratio while choosing an even H.264-compatible height.
🚩 plot
Usage:
# From the repo
python cli.py plot [FILE] [options]
# Installed entrypoint
lf plot [FILE] [options]
Key Options:
FILE(required for plotting): CSV/TSV file path. Not required for--config-show, or for--configwith no plotting.-d, --delimiter DELIM: Force delimiter (auto-sniff if omitted across , \t ; | space).-c, --config [PROFILE]: Load a plot config profile from~/.config/lf-toolbox/plot.defaults.json. OmittingPROFILEusesdefault. Explicit CLI flags override config values.--config-show: Create/open~/.config/lf-toolbox/plot.defaults.jsonin your editor (or system opener) and exit.-t, --title TEXT: Window title.-x, --xcol NAME|INDEX: Column to use as X axis (time-like, numeric, or fallback to row indices). Default: first column.-s, --scale FLOAT: Multiply plotted X values by this factor (default1.0). Useful for converting frame/sample index to time (numeric/index x-axis only).-y, --ycols COLS: Comma-separated list of Y columns (names or indices). Default: all numeric except the chosen x column.--xlim start,end: Scaled x-value slice (inclusive) before plotting. Accepts comma or colon:--xlim 10,20or--xlim 10:20. Empty start/end allowed (,20or10,).-e, --export: Export a high‑resolution PNG (ImageExporter; independent of window size) next to the CSV (same basename) and exit. Env overrides:PLOT_EXPORT_WIDTH,PLOT_EXPORT_PER_PLOT.-w, --weight FLOAT: Width/size control (in pixels, default 1.0). In line mode it sets line width; in--points-onlymode it sets marker size and marker outline width.-o, --out-path PATH: Output PNG path or directory (implies--exportif not explicitly provided). If a directory or ends with a path separator, the file name<csv_basename>.pngis used..pngextension appended if missing.
Config file (--config [PROFILE])
- Config root is a JSON object whose keys are profile names.
-cuses thedefaultprofile;-c imuuses theimuprofile. - Each profile supports:
delimiter,title,scale,export,out_path,xcol,ycols,xlim,weight,points_only. xcolaccepts string or integer.ycolsaccepts either comma-separated string ("20,21,22") or a list ([20, 21, 22]).- Precedence: explicit CLI flags always win over config values.
- Config path is fixed:
~/.config/lf-toolbox/plot.defaults.json. --config-showcreates the file if missing, opens it for editing, then exits.--configwithoutFILEalso opens the config file and exits.- Old flat config files are migrated automatically to
{ "default": { ...old options... } }with a timestamped.bakbackup next to the original file.
Automatic X-axis detection:
- If selected x column parses as (mostly) datetimes or epoch seconds/milliseconds -> time axis (DateAxisItem).
- Else if (mostly) numeric -> numeric index using the column’s values.
- Else -> simple row index (0..N-1).
Y column selection:
- Columns with ≥ ~60% numeric entries qualify automatically (unless overridden with
--ycols). - Non-numeric cells become gaps (NaN) in the plot.
X-value trimming (--xlim):
- Applied to plotted x-axis values after
--scaleis applied. - Inclusive range filter: keep points where
xmin <= x <= xmax. - If bounds exceed real data extents, they are clamped to available x min/max.
- Example: with
-s 0.02,--xlim 27200,27500clamps to data bounds when needed. --xlim ,20keeps data from min x through 20;--xlim 10,keeps 10 through max x.
White Theme:
- Background forced to white; axes/text black; subtle grid (alpha 0.15).
- Exported PNG uses the same theme.
Examples:
# Quick auto plot
lf plot ~/data/sensors.csv
# Tab-delimited with custom title
lf plot ~/data/log.tsv -d $'\t' -t "Device Log"
# Explicit x column by name and selected y columns
lf plot data.csv -x timestamp -y temperature,pressure,3
# Restrict to x values 200..300 (after scaling, if -s is used)
lf plot data.csv --xlim 200,300
# Option before positional
lf plot --xlim 200,300 data.csv
# Option after positional (equivalent)
lf plot data.csv --xlim 200,300
# Convert frame_id to seconds at 50 Hz (period = 0.02 s)
lf plot data.csv -x frame_id -s 0.02 -y acc_x,acc_y,acc_z
# First-time setup: create/open config, then edit and rerun
lf plot --config-show
# Quick edit flow (also opens config and exits)
lf plot --config
# Reuse the default profile from fixed config path
lf plot data.csv --config
# Reuse a named profile
lf plot data.csv --config imu
# Override one config value for a specific run
lf plot data.csv --config imu -x 6
# Export a high-res PNG (no GUI)
lf plot data.csv -y acc_x,acc_y,acc_z -e
# Export to a specific file path (auto-enables export mode)
lf plot data.csv -o ~/Desktop/plots/session1.png
# Custom export size via environment
PLOT_EXPORT_WIDTH=3000 PLOT_EXPORT_PER_PLOT=250 lf plot data.csv -e
# Thicker lines for visibility
lf plot data.csv -y acc_x,acc_y,acc_z -w 2.5
# Smaller points in points-only mode (weight controls marker size)
lf plot data.csv --points-only -w 1
Example ~/.config/lf-toolbox/plot.defaults.json:
{
"default": {
"xcol": 6,
"ycols": [20, 21, 22, 23, 24, 25, 11, 12, 14, 9, 10, 15, 16],
"scale": 0.02
},
"imu": {
"xcol": "frame_id",
"ycols": ["acc_x", "acc_y", "acc_z"],
"scale": 0.02,
"points_only": true
}
}
Console Output Summary: (example)
Loaded 6000 rows, 18 columns from: data.csv
Using x-axis: index (column: time[0])
Y subplots: (6 / 17)
Selected channels (6): acc_x[3], acc_y[4], acc_z[5], gyr_x[6], gyr_y[7], gyr_z[8]
Unselected channels (11): temp[9], pressure[10], battery[11], state[12], ...
Data points (x): [20,30] (501 / 6000 points selected)
--xlim [20,99] clamped to data range [20,30]
Saved PNG: data.png (width=2400px, plots=6, line-width=2.5)
Notes:
- Channel indices in brackets are zero-based column positions from the original CSV.
- Unselected list excludes the chosen x-axis column.
- When no trimming is applied you will see
(full dataset)instead of a selected fraction.
Requirements:
- Desktop GUI environment (macOS, Windows, Linux with X11/Wayland).
- Dependencies:
PyQt6,pyqtgraph(plus optionalpython-dateutilfor richer date parsing).
Keyboard Shortcuts (interactive mode):
Esc: Close the window and exit.
Other Behaviors:
- Supplying
--out-pathwithout--exportprints a note and performs an export (no interactive session). - Rows with non-numeric Y values render gaps (NaN) in lines instead of aborting.
Planned (possible future additions): trimmed-segment CSV export, interactive ROI selection, overlay/legend mode, optional scaled vector/SVG export.
🚩 note
Create and manage timestamped Markdown notes under ~/Documents/notes.
Usage:
# Create a note in the main notes folder
lf note --new "Project ideas"
# Create a categorized note; missing subfolders are created automatically
lf note --new "API decision" -f work/backend
# List notes as a directory tree
lf note -l
# List only one subfolder
lf note -l -f work
# Open by exact relative path, filename, or unique partial match
lf note api-decision
lf note work/backend/20260725-160000_api-decision.md
# Open a notes folder in Finder or the platform file browser
lf note
lf note -f work
# Open the folder URL in the default web browser
lf note -b
# List in the terminal and open the same subfolder in the web browser
lf note -l -b -f work
# Create/open the configuration file
lf note -c
New filenames follow YYYYmmdd-HHMMSS_<normalized-title>.md. For example:
20260725-160000_project-ideas.md
The original title is written as the Markdown heading. Filename characters are normalized for portability across macOS, Linux, and Windows. If the same title is created within one second, a numeric suffix prevents overwriting.
Options:
-c, --config: Create/open~/.config/lf-toolbox/note.defaults.jsonand exit.-l, --list: List notes as a recursive directory tree. Folders are alphabetical and notes within each folder are newest first.-b, --browse: Open the selected notes folder as afile://URL in the default web browser. It may be combined with--list.-n, --new TITLE: Create and open a new timestamped note.-f, --subfolder PATH: Restrict creation, listing, opening, or browsing to a relative subfolder.
Positional text opens an exact or uniquely matching existing note. With no positional text or action, note opens the selected notes folder in Finder or the platform file browser; explicit --browse instead uses the default web browser. Subfolders are ordinary filesystem folders and are created automatically when browsing or creating. They may be moved or renamed in Finder or another file manager; recursive listing and matching discover the current structure without an index or database. Absolute paths and parent traversal (..) are rejected for --subfolder.
The configuration is loaded automatically whenever note runs:
{
"notes_dir": "~/Documents/notes",
"editor": null,
"browser": null,
"add_title_heading": true
}
Editor selection uses the configured editor, then VISUAL, then EDITOR. The platform fallback is Nano on macOS, Nano or Vi on Linux, and Notepad on Windows. Editor commands may include arguments, such as "code --wait".
The browser setting is an optional command template for --browse; %s is required and is replaced with the selected folder’s file:// URL. Common settings are:
- System default:
"browser": null - macOS Chrome:
"browser": "open -a \"Google Chrome\" %s" - Windows Chrome:
"browser": "cmd /c start chrome %s" - Windows Edge:
"browser": "cmd /c start msedge %s" - Linux Chrome:
"browser": "google-chrome %s" - Linux Firefox:
"browser": "firefox %s"
🚩 coffee
Keep a Mac awake while a local Cursor agent is working, including with the lid closed, then allow sleep when that work is finished. Cloud agents do not hold the machine awake.
Foreground is the default so the terminal shows the session. A line is printed only when the lid, the active agent, or the sleep hold changes (--verbose prints every poll).
07:43:01 lid open (best-effort; AppleClamshellState=No)
07:44:10 agent busy: Fix cache (id abc, workspace ~/src/api)
07:44:10 hold acquired (Amphetamine session via osascript, display sleep allowed, 12h)
07:44:40 lid closed (best-effort; AppleClamshellState=Yes)
07:44:40 agent busy: Fix cache (id abc, workspace ~/src/api)
07:51:02 agent finished: Fix cache (id abc, workspace ~/src/api)
07:51:02 all local agents idle; releasing sleep hold
07:51:02 sleeping / hold released (Amphetamine session ended)
If Amphetamine is missing or not ready for a closed lid, coffee says so and names the switch to pmset before it holds sleep:
07:43:01 Amphetamine is not installed, so coffee cannot use it.
07:43:01 Setup: install Amphetamine from the Mac App Store (free). `lf coffee setup` opens that page.
07:43:01 Setup: uncheck Session Defaults → "Allow system sleep when display is closed" once. Amphetamine's scripting API cannot change that checkbox, and coffee will not change it.
07:43:01 Using pmset instead (`sudo pmset -a disablesleep 1`) until Amphetamine is installed and that checkbox is off.
07:43:01 `lf coffee --pmset` is the zero-setup path and does not need Amphetamine.
07:43:01 backend: pmset (Amphetamine is not installed; using pmset instead)
lf coffee
lf coffee --idle-seconds 90 --poll-seconds 15
lf coffee --pmset
lf coffee --allow-battery
lf coffee --dry-run
lf coffee --verbose
lf coffee setup
lf coffee status
lf coffee stop
Lid state comes from IORegistry AppleClamshellState (Yes means closed). That sensor is best-effort: desktops and some VMs do not expose it, and the line says so.
Backends
--pmsetis zero-setup. It does not need Amphetamine. coffee runssudo pmset -a disablesleep 1and says that this path was requested.- Amphetamine, when
/Applications/Amphetamine.app(or~/Applications/Amphetamine.app) is installed and Session Defaults are saved with Allow system sleep when display is closed unchecked. coffee reads that preference (Allow Closed-Display Sleep= false) and never writes it. The session allows display sleep (the screen may go black). Amphetamine's scripting API cannot toggle the checkbox. - Otherwise coffee prints those setup steps and uses
sudo pmset -a disablesleep 1. The log says Amphetamine was not ready and that pmset is the backend for this run.lf coffee statusrepeats that. The first foreground start also opens the App Store page, or Amphetamine if it is installed but the checkbox is still on. Later starts keep printing the steps and do not open the page again.lf coffee setupopens it on demand (mas openwhen themasCLI is installed, otherwise the public App Store URL; the app itself when it is already installed). - sudo may prompt once for pmset. The previous value is restored on normal exit, Ctrl+C / SIGTERM, and if the coffee process dies (a detached helper plus a lock file under
~/.config/lf-toolbox/coffee/). If both the daemon and the helper are killed, the nextlf coffee,lf coffee status, orlf coffee stopclears a leftoverdisablesleep.caffeinateis not used; it does not keep the Mac awake with the lid closed.
AC power is required unless --allow-battery is set. On battery, coffee reports the agent but does not hold sleep.
Detection is heuristic. There is no Cursor API for "the agent is generating". coffee watches:
~/.cursor/projects/<project>/agent-transcripts/**/*.jsonl(local IDE and cursor-agent transcripts, including subagents)~/.cursor/chats/<hash>/<id>/store.db(local cursor-agent store)~/Library/Application Support/Cursor/User/globalStorage/state.vscdbcomposer headers andcomposerData:<id>(statussuch asgenerating)
A fresh transcript counts as busy. Plan approval, CreatePlan, AskQuestion, and phrases such as "waiting for you to execute" release the hold immediately. Otherwise the hold drops after --idle-seconds (default 120) without local agent output. Composer rows tagged as background/cloud agents are ignored. A generating status that never updates again is ignored after 30 minutes. Tune --idle-seconds if a long silent tool call releases the hold early.
Project Structure
cli.py # Main CLI entry point
tools/
_cli_common.py # Shared Typer app factory
_deps.py # Runtime dependency install prompts
_cli_output.py # Shared CLI output helpers
tree.py # Directory tree tool
youtube.py # YouTube downloader tool
clock.py # Full-screen seven-segment terminal clock
cheque.py # HK cheque wording (Chinese + English)
compress.py # PDF/video compression helpers
plot.py # CSV plotting with pyqtgraph (PyQt6)
note.py # Timestamped Markdown note manager
coffee.py # macOS awake hold while a local Cursor agent works
coffee_activity.py # Local-agent activity heuristic
coffee_awake.py # Amphetamine / pmset hold and restore
tests/
test_cheque.py
test_note.py
test_compress.py
test_tree.py
test_coffee.py
License
See LICENSE for details.
Metadata
Release files for lf-toolbox 1.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lf_toolbox-1.16.0.tar.gz | 104.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lf_toolbox-1.16.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 186.4 kB
Release files / lf_toolbox-1.16.0.tar.gz
| Download URL | lf_toolbox-1.16.0.tar.gz |
|---|---|
| Size | 104.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f93c93dcb9a903da8af9419b5e80d3f948945c909656dc048a8757d2cc84c842
|
|
BLAKE2b-256 checksum How to use checksums |
4d10a0bdfebb2b132425a6c82ff1b747fda720855332f89c0fef665bd0e03c12
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logRelease files / lf_toolbox-1.16.0-py3-none-any.whl
| Download URL | lf_toolbox-1.16.0-py3-none-any.whl |
|---|---|
| Size | 82.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d96797dec1fcd29a7c9cb398d63d2d40935d7a2bc8da21a6e333df6325a3d54d
|
|
BLAKE2b-256 checksum How to use checksums |
7af2cc15f2ba3aae00d4350cef760b60924e172ff1c0502d43becbf5e92fa689
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logSecurity for lf-toolbox 1.16.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lf_toolbox-1.16.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.