Files
Arnav 77d74e0261 feat(config): XDG-style config location, with legacy fallback (#172) (#173)
* feat(config): resolve global config from an XDG-style location (#172)

The global config lived at ~/.obsidian-wiki, which isn't XDG-compliant.
It now resolves to $XDG_CONFIG_HOME/obsidian-wiki (default
~/.config/obsidian-wiki).

Existing installs are unaffected: if ~/.obsidian-wiki is present and the
XDG path is not, the legacy directory is used as-is. No migration is
required, and nothing moves on upgrade. Once the XDG path exists it wins,
so `mv ~/.obsidian-wiki ~/.config/obsidian-wiki` is all a migration takes.

Resolution is implemented identically in Python (cli.py), bash (setup.sh,
scripts/daily-update.sh, scripts/wiki-notify.sh), and the Config
Resolution Protocol that every skill defers to, so vault-scoped state and
named `config.<name>` profiles stay in one place.

* fix(config): stop setup from wiping user-added config keys

`write_config` rewrote the global config with only the three setup-managed
keys, so every `obsidian-wiki setup` — including the one a pip upgrade
prompts for — silently destroyed anything else in the file:
OBSIDIAN_LINK_FORMAT, QMD_WIKI_COLLECTION, WIKI_SKIP_PROJECTS, sync
settings, and comments.

Setup now owns only OBSIDIAN_VAULT_PATH, OBSIDIAN_WIKI_REPO and
OBSIDIAN_WIKI_VERSION, updating those in place and carrying every other
line over untouched. Duplicate definitions of a managed key collapse to
one.

Predates the XDG change (same behavior on main) but is fixed here because
this branch already touches config writing, and the XDG work makes
re-running setup more likely.

Also pins PYTHONPATH in both new test files so they exercise this
checkout rather than whatever obsidian_wiki happens to be installed.
2026-08-16 20:40:09 -07:00

131 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Daily wiki index update — called by launchd or directly.
# Checks if any history sources are stale and writes vault-scoped state files
# that the shell prompt reads on terminal open.
#
# Config resolution order (mirrors llm-wiki/SKILL.md protocol):
# 1. Walk up from CWD looking for .env with OBSIDIAN_VAULT_PATH
# 2. Fall back to the global config (XDG-style; legacy ~/.obsidian-wiki honored)
# 3. Exit with error if neither found
# XDG-style global config dir, with legacy ~/.obsidian-wiki fallback for
# installs that already have it (see llm-wiki/SKILL.md Config Resolution Protocol).
_obsidian_wiki_config_dir() {
local xdg_dir="${XDG_CONFIG_HOME:-$HOME/.config}/obsidian-wiki"
local legacy_dir="$HOME/.obsidian-wiki"
if [[ -d "$legacy_dir" && ! -e "$xdg_dir" ]]; then
echo "$legacy_dir"
else
echo "$xdg_dir"
fi
}
_find_config() {
local dir="$PWD"
while [[ "$dir" != "$HOME" && "$dir" != "/" ]]; do
if [[ -f "$dir/.env" ]] && grep -q "OBSIDIAN_VAULT_PATH" "$dir/.env" 2>/dev/null; then
echo "$dir/.env"
return
fi
dir="$(dirname "$dir")"
done
local global_config
global_config="$(_obsidian_wiki_config_dir)/config"
if [[ -f "$global_config" ]]; then
echo "$global_config"
return
fi
echo ""
}
CONFIG_FILE="$(_find_config)"
if [[ -z "$CONFIG_FILE" ]]; then
echo "[wiki-daily] No config found. Run wiki-setup to initialize your wiki." >&2
exit 1
fi
# shellcheck source=/dev/null
source "$CONFIG_FILE"
if [[ -z "${OBSIDIAN_VAULT_PATH:-}" ]]; then
echo "[wiki-daily] OBSIDIAN_VAULT_PATH not set in $CONFIG_FILE — skipping" >&2
exit 1
fi
# Vault-scoped state dir (supports multiple vaults independently)
VAULT_ID=$(echo "$OBSIDIAN_VAULT_PATH" | md5sum 2>/dev/null | cut -c1-8 || \
md5 -q - <<< "$OBSIDIAN_VAULT_PATH" 2>/dev/null | cut -c1-8 || \
echo "default")
STATE_DIR="$(_obsidian_wiki_config_dir)/state/$VAULT_ID"
mkdir -p "$STATE_DIR"
# Write vault path so wiki-notify.sh can find this state dir
echo "$OBSIDIAN_VAULT_PATH" > "$STATE_DIR/.vault_path"
MANIFEST="$OBSIDIAN_VAULT_PATH/.manifest.json"
# Count sources modified after last ingest
stale_count=0
if [[ -f "$MANIFEST" ]]; then
last_updated=$(python3 -c "
import json, sys
try:
d = json.load(open('$MANIFEST'))
print(d.get('last_updated',''))
except:
print('')
" 2>/dev/null || echo "")
if [[ -n "$last_updated" ]]; then
stale_count=$(MANIFEST="$MANIFEST" OBSIDIAN_VAULT_PATH="$OBSIDIAN_VAULT_PATH" python3 - <<'PYEOF'
import json, os, sys
from datetime import datetime, timezone
manifest_path = os.environ["MANIFEST"]
try:
with open(manifest_path) as f:
manifest = json.load(f)
except Exception:
print(0)
sys.exit()
last_updated_str = manifest.get("last_updated", "")
try:
last_updated = datetime.fromisoformat(last_updated_str.replace("Z", "+00:00"))
except Exception:
print(0)
sys.exit()
if last_updated.tzinfo is None:
last_updated = last_updated.replace(tzinfo=timezone.utc)
vault_path = os.environ.get("OBSIDIAN_VAULT_PATH", "")
stale = 0
for path, meta in manifest.get("sources", {}).items():
expanded = os.path.expanduser(path)
if not os.path.isabs(expanded) and vault_path:
expanded = os.path.join(vault_path, expanded)
if os.path.exists(expanded):
mtime = datetime.fromtimestamp(os.path.getmtime(expanded), tz=timezone.utc)
if mtime > last_updated:
stale += 1
print(stale)
PYEOF
)
fi
fi
# Write vault-scoped state
NOW=$(date +%s)
echo "$NOW" > "$STATE_DIR/.last_update"
echo "$stale_count" > "$STATE_DIR/.pending_delta"
if [[ "$stale_count" -gt 0 ]]; then
echo "[wiki-daily] $stale_count source(s) have new content since last ingest. State: $STATE_DIR"
else
echo "[wiki-daily] Wiki is up to date. State: $STATE_DIR"
fi