Skip to content

Latest commit

 

History

History
183 lines (128 loc) · 10.4 KB

File metadata and controls

183 lines (128 loc) · 10.4 KB

FunBun: a CLI to-do app

FunBun is a minimalistic to-do app for manipulating the task list in the terminal with the least possible amount of keystrokes.

It uses:

  • A predefined set of simple Markdown files to store tasks (one line = one task)
  • A script for manipulating the lines using TypeScript and Bun (having Bun installed is a prerequisite)
  • Short shell aliases/functions to call different functions of the script quickly

The files and the script are stored in one folder called "fun", shell aliases/functions are generated by a script function and stored in the shell config file (.zshrc by default).

File structure

  • a.md — active tasks (what should be done today)
  • b.md — backlog (non-critical tasks)
  • c.md — completed tasks (tasks from a.md and b.md get here after completion)
  • d.md — deleted tasks (tasks from a.md and b.md get here if they are discarded)
  • g.md — global goals (tasks are derived from them by the user)
  • fun (possibly with some extension like .sh, .py or .ts) — the script itself

Format

Each line in the text files represents a task. The line contents consist of:

  • a Markdown completess indicator with a space character after it
  • a brief task title in lowercase, optionally showing decomposition using colons
  • optionally: a UNIX timestamp of completion/deletion time

Examples:

- [ ] setup: cmus mediakeys

That line shows an uncompleted task, which is a decomposed subtask "cmus mediakeys" of some larger "setup" goal

- [x] fix boiler 1767265856

That means that a task to fix the boiler did not require decomposing into parts and was marked as completed at the UNIX time 1767265856.

Functionality and aliases

The script should have functions like "show" or "add". Running the scripts with parameters should invoke them, so "bun fun.ts show a" would mean "show the contents of the a.md file".

The script should be written considering that it would mostly be used with shell aliases (preferably) and shell functions. A simple shell alias "a" will be turn into something like a "bun ~/fun/fun.ts show a", letting the user see the main task list with just a single-letter command. So the functionality should be exposed for those aliases listed below.

In the cases where shell aliases are not enough for the required functionality (e.g., passing task title as the parameter), shell functions should be used. They too should invoke corresponding parts of the script, possibly by using something like "bun ~/fun/fun.ts add b task title" (that would append the b.md file with a new line containing the completeness status and the task title).

There also use cases where vim is used. In that case there is no need to invoke the script at all. The aliases should be mapped just to opening the corresponding files with vim.

Full list of shell aliases/functions and the script functionality they invoke:

  1. Alias "fun" should call the script itself in the default mode (no parameters). In that case the script should show a help page, informing about the functions it has and the aliases mapped to them. Also, it should check what shell is currently used. If it is not zsh or bash, the script should show a warning above that help page that the current environment is considered non-standard and the script might require some tweaking to be working to the full extent. If the shell is zsh or bash, the script should check whether its config file (.zshrc or .bashrc correspondingly) contains the "## funbun" line and whether the folder with the fun.ts script contains the project Markdown files. If something of that is missing, the script should show a warning above the page, stating that the project seems to be not initialized yet, and informing that it can be done by running the script with the "init" parameter.

  2. A letter corresponding to a file ("a" corresponds to "a.md", and so on with letters b, c, d, g) with no argument: outputs the content of the corresponding file, adding some formatting to make it more informative and better looking (showing line numbers, adding a heading, changing the UNIX timestamps into human-readable dates, and possibly more).

  3. A letter corresponding to a file, immediately preceded by "f" ("fa", "fb", "fc", "fd", "fg"): opens the corresponding file in vim, using the script is not required

  4. A letter "a" or "b" followed by some arguments after a space: it takes those arguments as a task title and adds to a new line in the corresponding file (a.md or b.md) with an indicator showing the incomplete status.

  5. A letter "c" or "d" followed by a number after a space: it should search for the task with corresponding line number in a.md to mark it completed/deleted accordingly. It means appending the task to the corresponding file (c.md or d.md) with the timestamp of that action added to the end of the line, and also manipulating the a.md: in the case of completion a task its indicator changes to "completed" in a.md (no adding timestamp), in the case of deletion this line gets deleted from a.md.

  6. A letter "s" with no argument: shows nicely formatted and motivating stats: the total number of tasks completed today, based on the timestamps; a total number of completed tasks; the total number of active tasks; a "streak" showing the number of days in a row at least one task was done; and list of the task titles done today. It should be formatted nicely, with the streak number highlighted in any way possible.

  7. A letter "s" followed by a number: used for decomposing a task. It takes a task with the corresponding line number from a.md and asks the user to decompose it into several smaller tasks. User enters subtask titles, each at a new line, until the user sends an empty line signalling the end of the list. Script takes all those subtask titles, removes the line in a.md containing the original task, and instead of it adds several lines containing subtasks, using the format "original task title: subtask title" in each line. They should "take place" of the original task, so if task on line 6 got split in 3 subtasks, they should occupy lines 6-8, pushing the tasks below (the ones previously on line 7 would move to line 9). It should be possible to "decompose decomposed" tasks, creating a task titles with two or more colons, but that probably would not happen often.

  8. A letter "r": used for "refreshing" the a.md file, removing all the completed tasks from it. We don't remove completed tasks immediately after the completion because it's motivating to see them in the current list and it helps to get a "daily review" before refreshing for the next day.

  9. The script should also have a function called "init", exposed as a parameter. That function should check if the files like "a.md" are present, and if not, create them. It should also check if the system shell config file (.zshrc or another, depending on the current shell) contains a line "## funbun", and if it doesn't, append that file with that line and all the listed shell aliases/shell functions below it, mapping those aliases to running the script with Bun in its current folder with the corresponding parameters.

  10. Other manipulations with tasks, such as editing the task titles or promoting backlog items into active ones, are currently meant to be done manually using vim, so they don't require script functionality for now. That might be possibly changed later.

Output examples

Showing a file (a, b, c, d, g)

Header showing the file's purpose, then numbered lines with formatted tasks:

ACTIVE TASKS
─────────────
☐ 1. setup: cmus mediakeys
☐ 2. fix boiler
✓ 3. reply to maria

For c.md and d.md, timestamps render as relative dates:

COMPLETED
─────────────
✓ 1. fix boiler (today, 14:32)
✓ 2. call bank (yesterday)
✓ 3. update resume (Dec 28)

Stats (s)

──────────────────────────
STREAK: ★ 12 days
TOTAL:  847 completed | 6 active
──────────────────────────
TODAY: 3 tasks completed
• fix boiler
• call bank
• buy milk
──────────────────────────

Streak uses bold/color if terminal supports it, plain asterisk otherwise.

Decompose prompt (s N)

Decomposing: setup cmus
Enter subtasks (empty line to finish):
> install cmus
> configure mediakeys
> test playback
>
Added 3 subtasks.

Edge cases

  • Line number out of range (c 99 when only 5 tasks): print error, do nothing
  • Empty file operations: succeed silently (no error for refreshing empty a.md)
  • Task title with special characters: allow anything except newlines
  • Duplicate task titles: allowed
  • Completing already-completed task: not possible (completed tasks only in c.md)
  • Running init when already initialized: safe to re-run, only adds missing pieces
  • Decomposing into zero subtasks (immediate empty line): cancel, keep original task

Errors

All errors print to stderr and exit with code 1. Success exits with 0.

Error messages:

  • "task N not found" — line number doesn't exist
  • "file not found: X.md — run 'fun init'" — missing task file
  • "not initialized — run 'fun init'" — aliases missing from shell config

Init details

  1. Creates missing .md files in the script's directory (empty files)
  2. Detects shell from $SHELL environment variable
  3. Appends to end of config file (~/.zshrc or ~/.bashrc)
  4. Alias block format:
## funbun
alias fun='bun ~/path/to/fun.ts'
alias a='bun ~/path/to/fun.ts show a'
alias fa='vim ~/path/to/a.md'
# ... etc for b, c, d, g
a() { [[ $# -eq 0 ]] && bun ~/path/to/fun.ts show a || bun ~/path/to/fun.ts add a "$@"; }
b() { [[ $# -eq 0 ]] && bun ~/path/to/fun.ts show b || bun ~/path/to/fun.ts add b "$@"; }
c() { [[ $# -eq 0 ]] && bun ~/path/to/fun.ts show c || bun ~/path/to/fun.ts complete "$@"; }
d() { [[ $# -eq 0 ]] && bun ~/path/to/fun.ts show d || bun ~/path/to/fun.ts delete "$@"; }
s() { [[ $# -eq 0 ]] && bun ~/path/to/fun.ts stats || bun ~/path/to/fun.ts split "$@"; }
alias g='bun ~/path/to/fun.ts show g'
alias r='bun ~/path/to/fun.ts refresh'
  1. If "## funbun" already exists, skip alias installation (print "aliases already installed")

Timestamps

  • All timestamps are UNIX seconds (UTC)
  • "Today" = same calendar date in system local timezone
  • Streak calculation: count consecutive calendar days (local time) with ≥1 completion
  • Display formatting:
    • Same day: "today, HH:MM"
    • Yesterday: "yesterday"
    • This year: "Mon DD"
    • Older: "Mon DD, YYYY"