···44VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
55LDFLAGS := -ldflags "-X main.version=$(VERSION)"
6677-.PHONY: build run install clean test send-test vet fmt tidy release docs help check-go
77+.PHONY: build run install clean test send-test vet fmt tidy release docs help check-go demo demo-reset
8899## check-go: verify Go is installed
1010check-go:
···3737 @echo "Installed to $(INSTALL)/$(BINARY)"
383839394040-initialized-welcome-screen:
4141- rm ~/.cache/neomd/welcome-shown
4040+## demo: run neomd with demo account (~/.config/neomd-demo/config.toml)
4141+demo: build
4242+ ./$(BINARY) -config $(HOME)/.config/neomd-demo/config.toml
4343+4444+## demo-reset: reset demo account to first-run state (welcome screen + empty screener lists)
4545+demo-reset:
4646+ ./scripts/reset-demo.sh $(HOME)/.config/neomd-demo
42474348## test: run all tests
4449test:
+15-5
internal/config/config.go
···150150 return filepath.Join(home, ".config", "neomd", "config.toml")
151151}
152152153153+// cacheDirName is derived from the config directory name (e.g. "neomd" or "neomd-demo").
154154+// Set during Load() so that different configs use separate cache directories.
155155+var cacheDirName = "neomd"
156156+153157// HistoryPath returns the path for the command history file.
154158// Uses the OS cache directory (~/.cache/neomd/ on Linux) so it is never
155159// picked up by dotfile version control but still persists across reboots.
156160func HistoryPath() string {
157161 if dir, err := os.UserCacheDir(); err == nil {
158158- p := filepath.Join(dir, "neomd")
162162+ p := filepath.Join(dir, cacheDirName)
159163 _ = os.MkdirAll(p, 0700)
160164 return filepath.Join(p, "cmd_history")
161165 }
162162- // Fallback: system temp dir with a user-scoped name
163166 return filepath.Join(os.TempDir(), fmt.Sprintf("neomd_%d_cmd_history", os.Getuid()))
164167}
165168166169// welcomePath returns the path of the first-run marker file.
167170func welcomePath() string {
168171 if dir, err := os.UserCacheDir(); err == nil {
169169- return filepath.Join(dir, "neomd", "welcome-shown")
172172+ return filepath.Join(dir, cacheDirName, "welcome-shown")
170173 }
171174 return filepath.Join(os.TempDir(), fmt.Sprintf("neomd_%d_welcome", os.Getuid()))
172175}
···192195 }
193196 path = expandPath(path)
194197198198+ // Derive cache dir name from config directory (e.g. "neomd-demo" from
199199+ // ~/.config/neomd-demo/config.toml) so demo and production don't share cache.
200200+ cacheDirName = filepath.Base(filepath.Dir(path))
201201+195202 cfg := defaults()
196203197204 if _, err := os.Stat(path); os.IsNotExist(err) {
···211218 cfg.Screener.PaperTrail = expandPath(cfg.Screener.PaperTrail)
212219 cfg.Screener.Spam = expandPath(cfg.Screener.Spam)
213220214214- // Ensure screener list directories exist so appending (I/O/F/P/$) works
215215- // on a fresh install without manual mkdir.
221221+ // Ensure screener list directories and files exist so appending (I/O/F/P/$)
222222+ // works on a fresh install without manual mkdir or touching files.
216223 for _, p := range []string{
217224 cfg.Screener.ScreenedIn, cfg.Screener.ScreenedOut,
218225 cfg.Screener.Feed, cfg.Screener.PaperTrail, cfg.Screener.Spam,
219226 } {
220227 if p != "" {
221228 _ = os.MkdirAll(filepath.Dir(p), 0700)
229229+ if _, err := os.Stat(p); os.IsNotExist(err) {
230230+ _ = os.WriteFile(p, nil, 0600)
231231+ }
222232 }
223233 }
224234