TypeScript/Bun CLI for archiving LiveJournal journal entries to local markdown files.
- Bun runtime (v1.0+)
bun installCopy the example env file and set your LiveJournal username:
cp .env.example .envThen edit .env:
LJ_USERNAME=myusername
This lets you run the CLI without passing a username every time. The .env file is gitignored and will never be committed.
Shortcut: the examples below use
bun run src/index.tsfor clarity, but the repo ships withcli.shandcli.ps1wrappers that invoke the CLI directly using an absolute path tosrc/index.ts, so they work from any working directory. You can replacebun run src/index.tswith./cli.sh(bash) or.\cli.ps1(PowerShell) in any command — e.g../cli.sh archive --year 2002.
bun run src/index.ts archivebun run src/index.ts archive myusernamebun run src/index.ts archive --year 2002Archive a window of N days starting from (and including) a specific date:
bun run src/index.ts archive --start-date 2002-12-20 --days 30Ranges can cross year boundaries — the CLI fetches each year's calendar page and filters down to the dates inside your window.
bun run src/index.ts archive [username] [options]
Options:
--year <year> Only archive a specific calendar year (e.g. 2002)
--start-date <YYYY-MM-DD> Start of a date range to archive (requires --days)
--days <n> Number of days to archive starting from --start-date (inclusive)
--limit <n> Max number of days to archive (omit for no limit)
--retries <n> Number of retries per page on failure (default: 3)
--delay <ms> Wait time in ms between requests (default: 1000)
--output <dir> Output directory (default: ./archive)
--verbose Enable verbose logging
--skip-existing Skip dates that already have a markdown file
--dry-run Show what would be archived without downloading or writing files
--include-comments Fetch and include user comments in archived markdown files
-h, --help Display help
-V, --version Display version
--year and --start-date/--days are mutually exclusive. If no date args are provided, the CLI discovers all available years from the user's calendar page and archives everything.
The username argument is optional. If omitted, the CLI reads LJ_USERNAME from your .env file. If neither is provided, the CLI exits with an error.
# Archive everything with a 2-second delay (username from .env)
bun run src/index.ts archive --delay 2000 --output ./my-journal
# Archive 2003 only, skip files already downloaded
bun run src/index.ts archive --year 2003 --skip-existing
# Verbose mode to see all requests
bun run src/index.ts archive --year 2005 --verbose
# Override .env username for a one-off run
bun run src/index.ts archive otherusername --year 2005
# Archive a 30-day window starting from a specific date
bun run src/index.ts archive --start-date 2002-12-15 --days 30
# Archive a single day
bun run src/index.ts archive --start-date 2002-01-24 --days 1
# Archive only the first 5 days (useful for quick testing)
bun run src/index.ts archive --limit 5
# Include user comments in archived files
bun run src/index.ts archive --year 2003 --include-comments
# Dry run — see what would be archived without writing any files
bun run src/index.ts archive --dry-run
# Dry run for a specific year
bun run src/index.ts archive --year 2002 --dry-run
# Dry run for a date range
bun run src/index.ts archive --start-date 2002-12-15 --days 30 --dry-runarchive/
livejournal.md ← table of contents (generated after archiving)
2002/
2002-01-24.md
2002-02-03.md
...
2003/
2003-01-15.md
...
Each markdown file contains all journal entries for that day:
# January 24, 2002
## Entry 1 - 04:34 pm
**Current Mood:** impressed
**Current Music:** mindless self indulgence - tight
hey this is my first post here...
---
## Entry 2 - 05:26 pm
sitting around right now...When --include-comments is used, each entry with comments gets a collapsible section appended beneath it:
## Entry 1 - 04:34 pm
hey this is my first post here...
<details>
<summary>3 comments</summary>
**[someuser](https://someuser.livejournal.com/)** — [Jan 24, 2002](https://example.livejournal.com/123.html?thread=456)
great post!
> **[anotheruser](https://anotheruser.livejournal.com/)** — [Jan 24, 2002](https://example.livejournal.com/123.html?thread=789)
> agreed!
</details>On interactive terminals, the CLI displays a polished TUI with animated spinners, progress bars, and colored output powered by @clack/prompts. When output is piped or running in CI, it falls back to plain [INFO]/[DEBUG] text with no ANSI escape codes.
# Run tests
bun test
# Build standalone binary
bun run buildsrc/
index.ts # Entry point
cli.ts # Commander CLI setup
types.ts # TypeScript interfaces
commands/
archive.ts # Archive command orchestration
scrapers/
calendar.ts # Discovers available years
year.ts # Discovers entry dates in a year
day.ts # Extracts entries from a day page
comments.ts # Fetches /{post-id}.html?nojs=1&view=comments → extracts comments with nesting depth across modern (b-tree-twig) and S1 legacy (ljcmt{id}) themes
converters/
html-to-markdown.ts # HTML to Markdown conversion
writers/
file-writer.ts # Writes markdown files to disk; builds table of contents
tui/
tty.ts # isTTY() check
logger.ts # TuiLogger with @clack/prompts (TTY only)
progress.ts # dualProgress bar renderer
utils/
date.ts # LocalDate helpers
http.ts # Fetch with exponential backoff retry
logger.ts # Leveled logger (plain text)
tests/
scrapers/ # Unit tests for scrapers
converters/ # Unit tests for converter
writers/ # Unit tests for file writer