A minimal email TUI where you read with Markdown and write in Neovim. neomd.ssp.sh/docs
email markdown neovim tui
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add demo account and fix reset correctly

sspaeti f8e1bcb4 69ea7e43

+70 -8
+8 -3
Makefile
··· 4 4 VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") 5 5 LDFLAGS := -ldflags "-X main.version=$(VERSION)" 6 6 7 - .PHONY: build run install clean test send-test vet fmt tidy release docs help check-go 7 + .PHONY: build run install clean test send-test vet fmt tidy release docs help check-go demo demo-reset 8 8 9 9 ## check-go: verify Go is installed 10 10 check-go: ··· 37 37 @echo "Installed to $(INSTALL)/$(BINARY)" 38 38 39 39 40 - initialized-welcome-screen: 41 - rm ~/.cache/neomd/welcome-shown 40 + ## demo: run neomd with demo account (~/.config/neomd-demo/config.toml) 41 + demo: build 42 + ./$(BINARY) -config $(HOME)/.config/neomd-demo/config.toml 43 + 44 + ## demo-reset: reset demo account to first-run state (welcome screen + empty screener lists) 45 + demo-reset: 46 + ./scripts/reset-demo.sh $(HOME)/.config/neomd-demo 42 47 43 48 ## test: run all tests 44 49 test:
+15 -5
internal/config/config.go
··· 150 150 return filepath.Join(home, ".config", "neomd", "config.toml") 151 151 } 152 152 153 + // cacheDirName is derived from the config directory name (e.g. "neomd" or "neomd-demo"). 154 + // Set during Load() so that different configs use separate cache directories. 155 + var cacheDirName = "neomd" 156 + 153 157 // HistoryPath returns the path for the command history file. 154 158 // Uses the OS cache directory (~/.cache/neomd/ on Linux) so it is never 155 159 // picked up by dotfile version control but still persists across reboots. 156 160 func HistoryPath() string { 157 161 if dir, err := os.UserCacheDir(); err == nil { 158 - p := filepath.Join(dir, "neomd") 162 + p := filepath.Join(dir, cacheDirName) 159 163 _ = os.MkdirAll(p, 0700) 160 164 return filepath.Join(p, "cmd_history") 161 165 } 162 - // Fallback: system temp dir with a user-scoped name 163 166 return filepath.Join(os.TempDir(), fmt.Sprintf("neomd_%d_cmd_history", os.Getuid())) 164 167 } 165 168 166 169 // welcomePath returns the path of the first-run marker file. 167 170 func welcomePath() string { 168 171 if dir, err := os.UserCacheDir(); err == nil { 169 - return filepath.Join(dir, "neomd", "welcome-shown") 172 + return filepath.Join(dir, cacheDirName, "welcome-shown") 170 173 } 171 174 return filepath.Join(os.TempDir(), fmt.Sprintf("neomd_%d_welcome", os.Getuid())) 172 175 } ··· 192 195 } 193 196 path = expandPath(path) 194 197 198 + // Derive cache dir name from config directory (e.g. "neomd-demo" from 199 + // ~/.config/neomd-demo/config.toml) so demo and production don't share cache. 200 + cacheDirName = filepath.Base(filepath.Dir(path)) 201 + 195 202 cfg := defaults() 196 203 197 204 if _, err := os.Stat(path); os.IsNotExist(err) { ··· 211 218 cfg.Screener.PaperTrail = expandPath(cfg.Screener.PaperTrail) 212 219 cfg.Screener.Spam = expandPath(cfg.Screener.Spam) 213 220 214 - // Ensure screener list directories exist so appending (I/O/F/P/$) works 215 - // on a fresh install without manual mkdir. 221 + // Ensure screener list directories and files exist so appending (I/O/F/P/$) 222 + // works on a fresh install without manual mkdir or touching files. 216 223 for _, p := range []string{ 217 224 cfg.Screener.ScreenedIn, cfg.Screener.ScreenedOut, 218 225 cfg.Screener.Feed, cfg.Screener.PaperTrail, cfg.Screener.Spam, 219 226 } { 220 227 if p != "" { 221 228 _ = os.MkdirAll(filepath.Dir(p), 0700) 229 + if _, err := os.Stat(p); os.IsNotExist(err) { 230 + _ = os.WriteFile(p, nil, 0600) 231 + } 222 232 } 223 233 } 224 234
+4
internal/imap/client.go
··· 190 190 } 191 191 var emails []Email 192 192 err := c.withConn(ctx, func(conn *imapclient.Client) error { 193 + emails = nil // reset on retry to avoid duplicates 193 194 if err := c.selectMailbox(folder); err != nil { 194 195 return err 195 196 } ··· 296 297 } 297 298 var uids []uint32 298 299 err := c.withConn(ctx, func(conn *imapclient.Client) error { 300 + uids = nil // reset on retry 299 301 if err := c.selectMailbox(folder); err != nil { 300 302 return err 301 303 } ··· 382 384 383 385 var uids []uint32 384 386 err := c.withConn(ctx, func(conn *imapclient.Client) error { 387 + uids = nil // reset on retry 385 388 if err := c.selectMailbox(folder); err != nil { 386 389 return err 387 390 } ··· 527 530 } 528 531 var emails []Email 529 532 err := c.withConn(ctx, func(conn *imapclient.Client) error { 533 + emails = nil // reset on retry 530 534 if err := c.selectMailbox(folder); err != nil { 531 535 return err 532 536 }
+43
scripts/reset-demo.sh
··· 1 + #!/bin/bash 2 + # Reset neomd demo to first-run state. 3 + # Only touches demo-specific files — never modifies production config or cache. 4 + # Usage: ./scripts/reset-demo.sh [config-dir] 5 + 6 + set -e 7 + 8 + CONFIG_DIR="${1:-$HOME/.config/neomd-demo}" 9 + DEMO_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/neomd-demo" 10 + 11 + echo "Resetting neomd demo state..." 12 + echo " Config dir: $CONFIG_DIR" 13 + echo " Cache dir: $DEMO_CACHE_DIR" 14 + echo 15 + 16 + # 1. Remove demo welcome marker 17 + if [ -f "$DEMO_CACHE_DIR/welcome-shown" ]; then 18 + rm -f "$DEMO_CACHE_DIR/welcome-shown" 19 + echo " [x] Removed welcome marker" 20 + else 21 + echo " [ ] Welcome marker already absent" 22 + fi 23 + 24 + # 2. Clear demo screener lists 25 + cleared=0 26 + for list in screened_in.txt screened_out.txt feed.txt papertrail.txt spam.txt; do 27 + path="$CONFIG_DIR/lists/$list" 28 + if [ -f "$path" ] && [ -s "$path" ]; then 29 + > "$path" 30 + cleared=$((cleared + 1)) 31 + fi 32 + done 33 + echo " [x] Cleared $cleared screener list(s)" 34 + 35 + # 3. Clear demo command history 36 + if [ -f "$DEMO_CACHE_DIR/cmd_history" ]; then 37 + rm -f "$DEMO_CACHE_DIR/cmd_history" 38 + echo " [x] Cleared command history" 39 + fi 40 + 41 + echo 42 + echo "Done! Next launch will show the welcome screen." 43 + echo "Run: make demo"