Project 2: task-nexus
Build a task manager CLI with nested subcommands, config hierarchy, and persistent storage.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2 (Intermediate) |
| Time Estimate | 1 week |
| Language | Go (Alternatives: Rust, Python) |
| Prerequisites | Project 1 or basic CLI parsing, file I/O |
| Key Topics | Subcommand trees, config precedence, persistence |
1. Learning Objectives
By completing this project, you will:
- Design a multi-level CLI command tree with shared flags.
- Implement deterministic config precedence across flags, env, and files.
- Persist data safely across runs and across OSes.
- Provide structured output for automation.
- Build a tool whose command layout can grow without chaos.
2. Theoretical Foundation
2.1 Core Concepts
- Command Trees: Tools like
gitanddockermap operations to a hierarchy. This makes discoverability and documentation scale. - Config Precedence: A predictable order prevents ambiguity: flags > env > config file > defaults.
- XDG Base Directory: Defines standard paths for config/data/cache on Unix; Windows has equivalent known folders.
- Persistence Models: JSON is simple and human-readable; SQLite handles concurrency and querying. Both are valid; the choice affects design.
2.2 Why This Matters
Most real CLIs are ecosystems. A tool with poorly designed subcommands becomes unusable as soon as it grows. This project teaches the structure that lets you add new capabilities without breaking users.
2.3 Historical Context / Background
git established conventions like subcommand help and command tree navigation. Modern CLI frameworks (Cobra, Clap) encode these conventions and help avoid inconsistent UX.
2.4 Common Misconceptions
- “Flat commands are simpler”: They become unintuitive when features grow.
- “Config is just a file”: It is a merge of multiple sources with precedence.
3. Project Specification
3.1 What You Will Build
A CLI tool named tasks with:
tasks add "Ship MVP" --priority hightasks list --status pendingtasks done 3tasks config set sync.url https://example.com
3.2 Functional Requirements
- Subcommands:
add,list,done,delete,config. - Persistence: Store tasks in a data file under the user data directory.
- Output Modes: Human table output and JSON output.
- Config Sources: Support config via flags, env, and config file.
3.3 Non-Functional Requirements
- Portability: Linux, macOS, Windows.
- Reliability: No data loss on crash during write.
- Discoverability:
--helpis clear for root and subcommands.
3.4 Example Usage / Output
$ tasks add "Ship MVP" --priority high
Task added: [1] Ship MVP
3.5 Real World Outcome
You add tasks, list them, and get both human-readable and machine-readable output:
$ tasks list --status pending
ID Task Priority Status Created
1 Ship MVP High Pending 2025-01-03
$ tasks list --output json
[{"id":1,"title":"Ship MVP","priority":"high","status":"pending"}]
This output can be piped into jq or used by scripts without parsing tables.
4. Solution Architecture
4.1 High-Level Design
+-------------+ +-----------------+ +-------------------+
| CLI Parser | --> | Command Handler | --> | Storage Layer |
| (Cobra) | | (add/list/etc.) | | (JSON/SQLite) |
+-------------+ +-----------------+ +-------------------+
| | |
+-------------------+-----------------------+
Shared config
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Root Cmd | Global flags, config | Output mode, config precedence |
| Subcommands | Implement operations | Keep naming stable |
| Storage | Read/write tasks | JSON vs SQLite |
| Formatter | Table or JSON | One output interface |
4.3 Data Structures
type Task struct {
ID int
Title string
Priority string
Status string
Created time.Time
}
type Store struct {
Version int
Tasks []Task
}
4.4 Algorithm Overview
Key Algorithm: Safe persistence
- Read store from disk.
- Apply mutation (add/done/delete).
- Write to temp file.
- Rename temp file into place (atomic on most OSes).
Complexity Analysis:
- Time: O(N) per operation
- Space: O(N) for file rewrite
5. Implementation Guide
5.1 Development Environment Setup
brew install go
mkdir task-nexus && cd task-nexus
go mod init task-nexus
5.2 Project Structure
task-nexus/
├── cmd/
│ ├── root.go
│ ├── add.go
│ ├── list.go
│ ├── done.go
│ └── config.go
├── internal/
│ ├── store/store.go
│ └── output/format.go
└── README.md
5.3 The Core Question You Are Answering
“How do I design a CLI that can scale from a handful of commands to a full product surface?”
5.4 Concepts You Must Understand First
- Command Trees
- Persistent vs local flags
- Help text inheritance
- Config Precedence
- Why deterministic override order matters
- User Data Locations
- XDG on Unix, AppData on Windows
5.5 Questions to Guide Your Design
- Should tasks live under config or data directories?
- How will you version the data schema?
- Should JSON output be default in CI?
5.6 Thinking Exercise
Sketch your command tree and list which flags should be global vs local.
5.7 The Interview Questions They Will Ask
- Why is a command tree better than flat flags?
- How do you prevent data loss during writes?
- What is the XDG Base Directory spec?
5.8 Hints in Layers
Hint 1: Start with only add and list.
Hint 2: Implement a Store interface for future backends.
Hint 3: Add --output json after table output works.
Hint 4: Use temp-file rename for safe writes.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| CLI design | “Build Awesome Command-Line Applications in Go” | Ch. 3-5 |
| Config hierarchy | “The Twelve-Factor App” | Factor III |
| File I/O | “The Linux Programming Interface” | Ch. 4 |
5.10 Implementation Phases
Phase 1: Foundation (1-2 days)
Goals:
- Root command +
add+list - JSON persistence
Checkpoint: Add and list tasks across runs.
Phase 2: Core Functionality (2-3 days)
Goals:
- Implement
doneanddelete - Add config loading
Checkpoint: Config values override defaults.
Phase 3: Polish and Output (1-2 days)
Goals:
- JSON output mode
- Improved help and error text
Checkpoint: tasks list --output json works.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Storage | JSON vs SQLite | JSON first | Simple and debuggable |
| Output | –json vs –output | –output | Extensible |
| File location | config vs data | data dir | Data is not config |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Store operations | add/done/delete |
| Integration Tests | CLI behavior | list output |
| Edge Case Tests | Empty store | list with zero tasks |
6.2 Critical Test Cases
- Add task then list shows it.
- Mark task done and list shows status change.
- Corrupt JSON triggers recovery message.
6.3 Test Data
{"version":1,"tasks":[{"id":1,"title":"Ship MVP","status":"pending"}]}
Expected:
tasks listshows one row with ID 1tasks done 1updates status
7. Common Pitfalls and Debugging
| Pitfall | Symptom | Solution |
|---|---|---|
| Wrong storage location | Data not persisted | Use UserConfigDir/UserDataDir |
| Data loss on crash | Missing tasks | Temp-file write + rename |
| Inconsistent output | Hard to parse | Keep output stable |
8. Extensions and Challenges
8.1 Beginner Extensions
- Add
tasks search <query> - Add
--priorityfiltering
8.2 Intermediate Extensions
- Add
tasks export --format csv - Add
tasks importfrom JSON
8.3 Advanced Extensions
- Add
tasks syncwith remote API - Add file locking for concurrent access
9. Real-World Connections
- CLI front-ends for internal tools
- Local developer productivity tooling
10. Resources
- Cobra and Viper docs
- taskwarrior source for inspiration
11. Self-Assessment Checklist
- I can explain config precedence
- I can describe a command tree and why it matters
12. Submission / Completion Criteria
Minimum Viable Completion:
addandlistwork- Data persists in user data directory
Full Completion:
- CRUD operations + config precedence
Excellence (Going Above and Beyond):
- Export/import + file locking or sync