config

 1import os
 2import sys
 3from pathlib import Path
 4
 5_PROJECT_ROOT = Path(__file__).resolve().parent
 6_APP_IDENTIFIER = "com.linxiv.app"  # must match src-tauri/tauri.conf.json "identifier"
 7
 8
 9def repo_dir() -> Path:
10    """The source-tree root. For developer/repo artifacts only (e.g. the dev .env).
11    NOT for runtime user data — that is data_dir()."""
12    return _PROJECT_ROOT
13
14
15def _default_data_dir() -> Path:
16    """OS per-user app-data dir for com.linxiv.app, matching Tauri's app_data_dir()
17    so a directly-launched CLI/MCP/API uses the same location as the packaged app.
18
19      Linux:   $XDG_DATA_HOME or ~/.local/share, then /com.linxiv.app
20      macOS:   ~/Library/Application Support/com.linxiv.app
21      Windows: %APPDATA% (Roaming) or ~/AppData/Roaming, then /com.linxiv.app
22
23    Used only when LINXIV_DATA_DIR is unset (dev / CLI / MCP launched without Tauri).
24    """
25    if sys.platform == "darwin":
26        base = Path.home() / "Library" / "Application Support"
27    elif sys.platform == "win32":
28        base = Path(os.environ.get("APPDATA") or (Path.home() / "AppData" / "Roaming"))
29    else:
30        base = Path(os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share"))
31    return base / _APP_IDENTIFIER
32
33
34def data_dir() -> Path:
35    """Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file.
36
37    The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess).
38    When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS
39    app-data dir — never the repo root. Resolved on every call so it tracks the env
40    var dynamically; call init_data_dir() once at startup to persist and create it.
41    """
42    d = os.environ.get("LINXIV_DATA_DIR")
43    return Path(d) if d else _default_data_dir()
44
45
46def init_data_dir() -> Path:
47    """Resolve, persist, and create the data dir. Call once per run at startup, before
48    any DB/PDF/vault access. Idempotent.
49
50    Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the
51    whole process (and inherited by child processes), then creates the directory. This
52    is what makes "initialize the data dir on any run" hold even when launched directly
53    without Tauri.
54
55    Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the
56    process. Tests that redirect the data dir must set/restore os.environ
57    (pytest's monkeypatch.setenv does this automatically).
58    """
59    path = data_dir()
60    os.environ["LINXIV_DATA_DIR"] = str(path)
61    path.mkdir(parents=True, exist_ok=True)
62    return path
63
64
65def resources_dir() -> Path:
66    """Read-only bundled resources: SQL schemas, graph assets, format templates.
67    In a PyInstaller frozen build these are extracted to sys._MEIPASS.
68    In development they live at the project root.
69    """
70    if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
71        return Path(sys._MEIPASS)  # type: ignore[attr-defined]
72    return _PROJECT_ROOT
73
74
75# .env file location — developer convenience for dev API keys. Lives in the repo (a
76# source artifact), NOT in data_dir(). In production Tauri injects env vars directly;
77# this path just needs to exist.
78ENV_PATH = repo_dir() / ".env"
def repo_dir() -> pathlib.Path:
10def repo_dir() -> Path:
11    """The source-tree root. For developer/repo artifacts only (e.g. the dev .env).
12    NOT for runtime user data — that is data_dir()."""
13    return _PROJECT_ROOT

The source-tree root. For developer/repo artifacts only (e.g. the dev .env). NOT for runtime user data — that is data_dir().

def data_dir() -> pathlib.Path:
35def data_dir() -> Path:
36    """Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file.
37
38    The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess).
39    When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS
40    app-data dir — never the repo root. Resolved on every call so it tracks the env
41    var dynamically; call init_data_dir() once at startup to persist and create it.
42    """
43    d = os.environ.get("LINXIV_DATA_DIR")
44    return Path(d) if d else _default_data_dir()

Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file.

The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess). When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS app-data dir — never the repo root. Resolved on every call so it tracks the env var dynamically; call init_data_dir() once at startup to persist and create it.

def init_data_dir() -> pathlib.Path:
47def init_data_dir() -> Path:
48    """Resolve, persist, and create the data dir. Call once per run at startup, before
49    any DB/PDF/vault access. Idempotent.
50
51    Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the
52    whole process (and inherited by child processes), then creates the directory. This
53    is what makes "initialize the data dir on any run" hold even when launched directly
54    without Tauri.
55
56    Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the
57    process. Tests that redirect the data dir must set/restore os.environ
58    (pytest's monkeypatch.setenv does this automatically).
59    """
60    path = data_dir()
61    os.environ["LINXIV_DATA_DIR"] = str(path)
62    path.mkdir(parents=True, exist_ok=True)
63    return path

Resolve, persist, and create the data dir. Call once per run at startup, before any DB/PDF/vault access. Idempotent.

Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the whole process (and inherited by child processes), then creates the directory. This is what makes "initialize the data dir on any run" hold even when launched directly without Tauri.

Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the process. Tests that redirect the data dir must set/restore os.environ (pytest's monkeypatch.setenv does this automatically).

def resources_dir() -> pathlib.Path:
66def resources_dir() -> Path:
67    """Read-only bundled resources: SQL schemas, graph assets, format templates.
68    In a PyInstaller frozen build these are extracted to sys._MEIPASS.
69    In development they live at the project root.
70    """
71    if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
72        return Path(sys._MEIPASS)  # type: ignore[attr-defined]
73    return _PROJECT_ROOT

Read-only bundled resources: SQL schemas, graph assets, format templates. In a PyInstaller frozen build these are extracted to sys._MEIPASS. In development they live at the project root.

ENV_PATH = PosixPath('/home/uribe/Documents/linXiv/.env')