// Package paths mirrors behavision/paths.py. // // The two processes must agree on where state lives or they will quietly use // different databases: the engine would write footfall into one file while the // agent reads another and reports an empty store. The rule is the same on both // sides — BEHAVISION_DATA_DIR wins, then %PROGRAMDATA%\Behavision on Windows — // and `behavision paths` prints the engine's answer so the two can be compared // on a real machine rather than assumed equal. package paths import ( "os" "path/filepath" "runtime" ) const AppName = "Behavision" // StateRoot is the writable root: database, logs, spool, agent config. func StateRoot() string { if v := os.Getenv("BEHAVISION_DATA_DIR"); v != "" { if abs, err := filepath.Abs(v); err == nil { return abs } return v } if runtime.GOOS == "windows" { base := os.Getenv("PROGRAMDATA") if base == "" { base = `C:\ProgramData` } return filepath.Join(base, AppName) } home, err := os.UserHomeDir() if err != nil { return "." } if runtime.GOOS == "darwin" { return filepath.Join(home, "Library", "Application Support", AppName) } if v := os.Getenv("XDG_DATA_HOME"); v != "" { return filepath.Join(v, "behavision") } return filepath.Join(home, ".local", "share", "behavision") } // InstallRoot is the directory holding this executable. func InstallRoot() string { exe, err := os.Executable() if err != nil { return "." } if resolved, err := filepath.EvalSymlinks(exe); err == nil { exe = resolved } return filepath.Dir(exe) } func AgentConfig() string { return filepath.Join(StateRoot(), "agent.json") } func SpoolDir() string { return filepath.Join(StateRoot(), "spool") } func EngineLog() string { return filepath.Join(StateRoot(), "engine.log") } // APICredentials is the file the engine writes when it generates its own // Basic credentials. The agent reads it rather than storing a second copy, // so a regenerated credential does not silently break the tray. func APICredentials() string { return filepath.Join(StateRoot(), "data", "api_credentials.txt") } // EnsureState creates the writable tree. Called before anything opens a file // under it, so a first run on a fresh machine does not fail on a missing dir. func EnsureState() error { for _, d := range []string{StateRoot(), SpoolDir()} { if err := os.MkdirAll(d, 0o700); err != nil { return err } } return nil }