Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ go.work
.DS_Store

# Project specific
journal
/journal

/bin

# Backup files
*.bak
*~
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve

- Initial development items tracked here.

## [0.2.02] - 2026-01-06

### Added

- `self-update` command to update the CLI directly from GitHub Releases.
- `make bump-version` command to automate version updates across files.
- Documentation for the update command in `README.md` and standard help output.

## [0.2.01] - 2026-01-06

### Added
Expand Down
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Variables
BINARY_NAME=journal
VERSION?=0.2.01
VERSION?=0.2.02
BUILD_DIR=bin
MAIN_PATH=cmd/journal/main.go
COVERAGE_FILE=coverage.out
Expand Down Expand Up @@ -103,3 +103,11 @@ vet: ## Run go vet
@echo "Running go vet..."
go vet ./...
@echo "Vet complete!"

bump-version: ## Bump version (usage: make bump-version v=1.0.0)
@if [ -z "$(v)" ]; then echo "Usage: make bump-version v=x.y.z"; exit 1; fi
@echo "Bumping version to $(v)..."
@sed -i.bak 's/^VERSION?=.*/VERSION?=$(v)/' Makefile && rm Makefile.bak
@sed -i.bak 's/const Version = .*/const Version = "$(v)"/' $(MAIN_PATH) && rm $(MAIN_PATH).bak
@sed -i.bak 's/version: .*/version: $(v)/' internal/help/help.yaml && rm internal/help/help.yaml.bak
@echo "Version bumped to $(v)"
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ Behavior of `--todos` mode:

Note: shells treat flags-without-values differently. Using `--todos ""` explicitly is reliable across shells to mean "today." If you prefer, I can add a separate boolean flag `--todo-mode` that always updates today's todos.

## Updates

To update `journal-cli` to the latest version, run:

```bash
sudo ./journal self-update
```

This will:
1. Validate the latest GitHub release.
2. Downloads the binary for your OS/Arch.
3. Replaces the current binary in-place.
4. Preserves the old binary as `.bak` in case of failure.

*Note: You may need `sudo` if the binary is installed in a protected directory.*

## Keywords

- journaling
Expand All @@ -140,4 +156,11 @@ Note: shells treat flags-without-values differently. Using `--todos ""` explicit

We welcome contributions! See `CONTRIBUTING.md` for guidelines on filing issues and submitting pull requests. Maintainers will review incoming PRs — the repository uses a review-first workflow and code owners to ensure one or more reviews are required before merging.

If you'd like to help, open an issue or submit a draft PR and we will guide you through the process.
If you'd like to help, open an issue or submit a draft PR and we will guide you through the process.

## Documentation

- [Architecture Overview](docs/ARCHITECTURE.md)
- [ADR 001: Pragmatic Clean Architecture](docs/adr/001-pragmatic-clean-architecture.md)
- [Feature: Self-Update](docs/features/self-update.md)
- [Feature: Templates](docs/features/templates.md)
50 changes: 0 additions & 50 deletions architecture.md

This file was deleted.

12 changes: 11 additions & 1 deletion cmd/journal/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,21 @@ import (

"journal-cli/internal/app"
"journal-cli/internal/help"
"journal-cli/internal/updater"
)

const Version = "0.2.01"
const Version = "0.2.02"

func main() {
// Check for "self-update" subcommand
if len(os.Args) > 1 && os.Args[1] == "self-update" {
if err := updater.Update(); err != nil {
fmt.Fprintf(os.Stderr, "Error updating: %v\n", err)
os.Exit(1)
}
return
}

helpFlag := flag.Bool("help", false, "Show help message")
version := flag.Bool("version", false, "Show version")
todos := flag.String("todos", "", "Update todos for a date (YYYY-MM-DD). Empty = today")
Expand Down
41 changes: 41 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Architecture Overview

`journal-cli` follows a **Pragmatic Clean Architecture**. The codebase is organized to separate domain logic from infrastructure concerns, though it favors simplicity over strict interface abstraction where appropriate for a CLI.

## Directory Structure

### `cmd/`
Entry points for the application.
- `cmd/journal/main.go`: The main entry point. Reads flags and hands control to the `app` package.

### `internal/`
Private application code.

- **`domain/`**: **(Inner Layer)**
- Contains pure data structures like `JournalEntry` and `Todo`.
- Has no dependencies on other internal packages.

- **`app/`**: **(Application Layer)**
- Contains the "glue" code.
- Orchestrates the program flow: Load Config -> Load Data -> Run TUI -> Save Data.
- Currently implements the primary "Use Cases" implicitly.

- **Infrastructure & Adapters**:
- `config/`: Handles loading and parsing `config.yaml`.
- `fs/`: File system helpers (wrappers for `os` and `io` with error handling).
- `markdown/`: Parsing and generation of Keep-a-Changelog style markdown journals.
- `tui/`: The Bubble Tea model and view logic. Handles the UI rendering and state machine.
- `updater/`: Handles the `self-update` mechanics (GitHub Releases API, binary replacement).
- `help/`: Structured help documentation logic.

## Data Flow

1. **Startup**: `main.go` parses flags.
2. **Orchestration**: `app.Run()` initializes config, template loader, and checks for existing files.
3. **Interaction**: Control is handed to `tui.NewModel()`. The Bubble Tea runtime manages the event loop (keystrokes -> update model -> view).
4. **Completion**: On exit, `app` retrieves the final state from the TUI model.
5. **Persistence**: `app` calls `markdown.GenerateMarkdown()` and then `fs.WriteFile()` to save logic to disk.

## Design Decisions

See [ADR 001: Pragmatic Clean Architecture](adr/001-pragmatic-clean-architecture.md).
45 changes: 45 additions & 0 deletions docs/adr/001-pragmatic-clean-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 1. Adoption of Pragmatic Clean Architecture

Date: 2026-01-06

## Status

Accepted

## Context

The `journal-cli` project aims to be a robust, maintainable CLI application. We want to structure the code in a way that separates concerns, making it testable and easy to extend. However, strict adherence to Clean Architecture (with full interface abstraction for every layer) can introduce unnecessary boilerplate for a CLI tool of this size.

## Decision

We will adopt a **Pragmatic Clean Architecture** style.

### Layers

1. **Domain (`internal/domain`)**:
- Contains pure business entities (e.g., `JournalEntry`, `Todo`).
- No external dependencies.

2. **Application (`internal/app`)**:
- Orchestrates the application flow (e.g., loading config, checking files, running the loop).
- Currently acts as both "Use Case" and "Controller".
- *Constraint*: Should prioritize business logic over UI specifics where possible.

3. **Adapters/Infrastructure (`internal/fs`, `internal/markdown`, `internal/tui`, `internal/updater`)**:
- Implement specific details (FileSystem, Markdown parsing, Terminal UI, GitHub Updates).
- The Application layer calls these helpers directly.

## Consequences

### Positive
- **Simplicity**: Less boilerplate than defining interfaces for every file operation or markdown parser.
- **Speed**: Faster development iteration.
- **Clarity**: Directory structure clearly indicates what each package does.

### Negative
- **Coupling**: `internal/app` is coupled to concrete implementations of `fs` and `tui`.
- **Testing**: We cannot easily mock the FileSystem or TUI in integration tests without refactoring `app` to use interfaces.

### Mitigation
- We accept this coupling for now.
- If a component (like `fs`) becomes complex or needs interchangeable backends (e.g., S3 support), we will refactor it behind an interface at that time.
30 changes: 30 additions & 0 deletions docs/features/self-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Feature: Self-Update

The `self-update` feature allows users to update their CLI binary to the latest version available on GitHub Releases without needing an external package manager.

## Usage

```bash
journal self-update
```

If installed in a protected directory (like `/usr/local/bin`):
```bash
sudo journal self-update
```

## Implementation Details

Located in: `internal/updater`

1. **Check**: Queries `https://api.github.com/repos/ops295/journal-cli/releases/latest`.
2. **Match**: Looks for an asset matching the pattern `journal-{GOOS}-{GOARCH}` (e.g., `journal-darwin-arm64`).
3. **Download**: Streams the binary to a temporary file in `os.TempDir`.
4. **Replace**:
- Moves the current binary to `{binary}.bak`.
- Moves the new binary to the current location.
- Restores from backup if the move fails.

## Constraints
- The binary must have write permissions to its own location.
- Access to GitHub API is required.
40 changes: 40 additions & 0 deletions docs/features/templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Feature: Templates

Templates allow users to define custom prompts for their daily journal entries.

## Configuration

Templates are YAML files stored in the `templates/` subdirectory of the config folder.

### Location
- **macOS**: `~/Library/Application Support/journal-cli/templates/`
- **Linux**: `~/.config/journal-cli/templates/`
- **Windows**: `%APPDATA%\journal-cli\templates\`

## File Format

Example `daily-reflection.yaml`:

```yaml
name: daily-reflection
description: A simple daily reflection
questions:
- id: gratitude
title: "What are you grateful for?"
- id: improvement
title: "What could have gone better?"
```

## Management Commands

- **List**: `journal --list-templates`
- **Set Default**: `journal --set-template <name>`
- Example: `journal --set-template daily-reflection`
- Logic: Sets `default_template` in `config.yaml`. The app will skip the template selection screen and auto-load this template.

## Implementation Details

Located in: `internal/template` (loading) and `internal/app` (management).

- `template.LoadTemplates()`: Scans the directory and parses valid YAML files.
- `app.SetDefaultTemplate()`: Updates the main `config.yaml` file.
9 changes: 9 additions & 0 deletions internal/help/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ func (h *HelpDoc) Render(programName string) string {
}
}
}
} else if section.Title == "Updates" {
sb.WriteString(" Examples:\n")
for _, cmd := range h.Commands {
if strings.Contains(cmd.Name, "update") && len(cmd.Examples) > 0 {
for _, ex := range cmd.Examples {
sb.WriteString(fmt.Sprintf(" %s\n", ex))
}
}
}
}
}

Expand Down
24 changes: 17 additions & 7 deletions internal/help/help.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
app:
name: journal-cli
description: A cross-platform terminal-based daily journaling application
version: 0.2.01
version: 0.2.02

commands:
- name: --help
Expand All @@ -13,26 +13,31 @@ commands:
- name: --list-templates
description: List available templates
examples:
- ./journal --list-templates
- journal --list-templates

- name: --set-template
args: <name>
description: Set default template
examples:
- ./journal --set-template daily-human-dev
- ./journal --set-template gentle-day
- journal --set-template daily-human-dev
- journal --set-template gentle-day

- name: --todo
description: Update today's todos (shorthand)
examples:
- ./journal --todo
- journal --todo

- name: --todos
args: "[YYYY-MM-DD]"
description: Update todos for a date (empty = today)
examples:
- ./journal --todos ""
- ./journal --todos 2025-12-30
- journal --todos ""
- journal --todos 2025-12-30

- name: self-update
description: Update journal to the latest version
examples:
- journal self-update

configuration:
title: Configuration
Expand Down Expand Up @@ -73,3 +78,8 @@ sections:
- "Use --todos [YYYY-MM-DD] to update todos (empty = today)"
- "Mark todos as complete, partial, or move to backlog"
- "Use --todo as a shorthand for updating today's todos"

- title: Updates
description: Keep your journal CLI up to date
items:
- "Use `self-update` to download and install the latest version from GitHub."
Loading
Loading