diff --git a/engine/hooks/daily-reflection.py b/engine/hooks/daily-reflection.py index 3df269e..9a014c6 100755 --- a/engine/hooks/daily-reflection.py +++ b/engine/hooks/daily-reflection.py @@ -17,7 +17,7 @@ sys.exit(0) sessions = [] -for line in index_file.read_text().splitlines(): +for line in index_file.read_text(encoding="utf-8").splitlines(): try: sessions.append(json.loads(line)) except Exception: @@ -30,7 +30,7 @@ tp = s.get("transcript_path") if tp and Path(tp).exists(): try: - transcripts.append(Path(tp).read_text()[:50000]) + transcripts.append(Path(tp).read_text(encoding="utf-8")[:50000]) except Exception: pass @@ -69,5 +69,5 @@ check=False, ) except Exception as e: - (LOGS / "daily-reflection-errors.log").open("a").write(f"{time.strftime(chr(37)+chr(70)+chr(84)+chr(37)+chr(84))} {e}" + chr(10)) + (LOGS / "daily-reflection-errors.log").open("a", encoding="utf-8").write(f"{time.strftime(chr(37)+chr(70)+chr(84)+chr(37)+chr(84))} {e}" + chr(10)) sys.exit(0) diff --git a/engine/hooks/session-indexer.py b/engine/hooks/session-indexer.py index 86bb753..9223e89 100755 --- a/engine/hooks/session-indexer.py +++ b/engine/hooks/session-indexer.py @@ -24,6 +24,6 @@ "cwd": payload.get("cwd") or os.getcwd(), "transcript_path": payload.get("transcript_path"), } -with (log_dir / f"sessions-{day}.jsonl").open("a") as f: +with (log_dir / f"sessions-{day}.jsonl").open("a", encoding="utf-8") as f: f.write(json.dumps(entry) + chr(10)) sys.exit(0) diff --git a/engine/hooks/session-logger.py b/engine/hooks/session-logger.py index a37e333..4099c92 100755 --- a/engine/hooks/session-logger.py +++ b/engine/hooks/session-logger.py @@ -23,6 +23,6 @@ "cwd": payload.get("cwd") or os.getcwd(), "transcript_path": payload.get("transcript_path"), } -with (log_dir / "sessions.log").open("a") as f: +with (log_dir / "sessions.log").open("a", encoding="utf-8") as f: f.write(json.dumps(entry) + chr(10)) sys.exit(0) diff --git a/engine/hooks/session-recap.py b/engine/hooks/session-recap.py index 1c9773c..0d8602d 100755 --- a/engine/hooks/session-recap.py +++ b/engine/hooks/session-recap.py @@ -68,7 +68,7 @@ def parse_transcript(path): if not path or not Path(path).exists(): return stats - with open(path, "r") as f: + with open(path, "r", encoding="utf-8") as f: for line in f: try: d = json.loads(line) @@ -206,9 +206,9 @@ def ensure_journal(day): f = JOURNAL_DIR / f"{day}.md" if not f.exists(): JOURNAL_DIR.mkdir(parents=True, exist_ok=True) - f.write_text(f"# {day} — Daily\n\n## 📊 Claude Sessions\n\n") - elif "## 📊 Claude Sessions" not in f.read_text(): - with f.open("a") as fh: + f.write_text(f"# {day} — Daily\n\n## 📊 Claude Sessions\n\n", encoding="utf-8") + elif "## 📊 Claude Sessions" not in f.read_text(encoding="utf-8"): + with f.open("a", encoding="utf-8") as fh: fh.write("\n## 📊 Claude Sessions\n\n") return f @@ -216,7 +216,7 @@ def ensure_journal(day): def upsert_session(journal, session_id, entry): """Replace existing block for this sid (or append). True if changed.""" sid_short = session_id[:8] - txt = journal.read_text() + txt = journal.read_text(encoding="utf-8") # Match existing block: header line + nested " - ..." continuation lines pattern = re.compile( rf"^- \*\*[^\n]+sid:`{re.escape(sid_short)}`[^\n]*\n(?: - [^\n]+\n?)*", @@ -228,7 +228,7 @@ def upsert_session(journal, session_id, entry): new_txt = new_txt.rstrip() + "\n\n## 📊 Claude Sessions\n\n" new_txt = new_txt.rstrip("\n") + "\n" + entry + "\n" if new_txt != txt: - journal.write_text(new_txt) + journal.write_text(new_txt, encoding="utf-8") return True return False @@ -276,7 +276,7 @@ def main(): # Log the error but never break the Stop hook err_log = Path.home() / ".claude" / "logs" / "session-recap-errors.log" try: - err_log.open("a").write(f"{time.strftime('%FT%T')} {type(e).__name__}: {e}\n") + err_log.open("a", encoding="utf-8").write(f"{time.strftime('%FT%T')} {type(e).__name__}: {e}\n") except Exception: pass sys.exit(0) diff --git a/engine/search/brain_search.py b/engine/search/brain_search.py index ecd21ca..b4ef725 100644 --- a/engine/search/brain_search.py +++ b/engine/search/brain_search.py @@ -23,6 +23,13 @@ import unicodedata from pathlib import Path +# Windows consoles default stdout/stderr to the system codepage (e.g. cp1252), +# which can't encode arrows/em-dashes/accents found in vault snippets and +# crashes with UnicodeEncodeError. Force UTF-8 so query output never crashes. +for _stream in (sys.stdout, sys.stderr): + if hasattr(_stream, "reconfigure"): + _stream.reconfigure(encoding="utf-8") + BRAIN = Path(os.environ.get("BRAIN_DIR") or (Path.home() / "Documents" / "Brain")) INDEX = Path(os.environ.get("BRAIN_SEARCH_INDEX") or (Path.home() / ".brain-search" / "index.json")) K1, B = 1.5, 0.75 diff --git a/install.ps1 b/install.ps1 index 8a54502..1ce5a37 100644 --- a/install.ps1 +++ b/install.ps1 @@ -119,7 +119,12 @@ if ($cur -match [regex]::Escape($mark)) { Say "Task Scheduler (reindex 04:00 + reflection 12:00/23:00)" function Register-BrainTask($name, $argument, $triggers) { $action = New-ScheduledTaskAction -Execute $Py -Argument $argument - Register-ScheduledTask -TaskName $name -Action $action -Trigger $triggers -Force -User $env:USERNAME | Out-Null + # LogonType S4U : tourne meme si personne n'est connecte/deverrouille (pas de mot de passe stocke). + # Le defaut de Register-ScheduledTask -User est LogonType Interactive, qui exige une session active - + # observe en pratique : echec (0x800710E0, "operateur/administrateur a refuse la requete") au declenchement + # de nuit/verrouille, succes seulement quand relance manuellement en session active. + $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited + Register-ScheduledTask -TaskName $name -Action $action -Trigger $triggers -Principal $principal -Force | Out-Null Ok "tache: $name" } try {