diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-05 14:04:40 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-05 14:04:40 +0200 |
| commit | ed4c217786ccb6f3c106f42209bcf31ac8871753 (patch) | |
| tree | 1eb76ae67ac22cfcc9763367701003fcb7d0a811 | |
| parent | f15665670b5f2923bb8ec84929b6bc9b7f29a53a (diff) | |
| download | fanfictioner-ed4c217786ccb6f3c106f42209bcf31ac8871753.tar.gz fanfictioner-ed4c217786ccb6f3c106f42209bcf31ac8871753.zip | |
Notify when waiting for input
Send a kitty desktop notification (OSC 99) whenever the script stops for
a decision: candidates ready or sd-cli failed, plan ready for review,
story changed, book done. Every notification shares one id, so each
replaces the previous one instead of stacking, and kitty shows them only
while the terminal is unfocused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rwxr-xr-x | fanfictioner | 13 |
1 files changed, 13 insertions, 0 deletions
diff --git a/fanfictioner b/fanfictioner index 8a4f164..72c2e85 100755 --- a/fanfictioner +++ b/fanfictioner @@ -416,6 +416,15 @@ def log(msg): f.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}\n") +def notify(body): + """Desktop notification via kitty (OSC 99). One fixed id, so each replaces the last instead of + stacking; shown only while the terminal is unfocused.""" + if sys.stdout.isatty(): + body = re.sub(r"[\x00-\x1f\x7f]", " ", body) + print(f"\x1b]99;i=fanfictioner:d=0:o=unfocused;fanfictioner\x1b\\" + f"\x1b]99;i=fanfictioner:d=1:p=body;{body}\x1b\\", end="", flush=True) + + def editor(path): subprocess.run([*shlex.split(os.environ.get("EDITOR") or "vi"), str(path)]) @@ -476,6 +485,7 @@ def review(d, profile, prompt, size, refs=(), skip=False): log(f"{stage} {profile} seed {seed}" + (f" FAILED: {err}" if err else "")) if not err: show(files, seed, d) + notify(f"{stage}: " + ("sd-cli failed" if err else "candidates ready, pick one")) while True: try: k = input(f"[{keys}] > ").strip().lower() @@ -571,6 +581,7 @@ def plan_stage(story, wd): meta, body = parse_front(story.read_text()) if md_path.exists() and (seen.read_text() != body if seen.exists() else story.stat().st_mtime > md_path.stat().st_mtime): + notify("story changed, plan needs a decision") try: k = input("\nstory changed since plan.md was drafted: " "[r]evise plan with Gemma [n]ew draft [k]eep plan [q]uit > ").strip().lower() @@ -611,6 +622,7 @@ def plan_stage(story, wd): print(f"\n{md}\n--- {md_path}: {len(plan['characters'])} characters, {len(plan['pages'])} pages") for e in errs: print(" !", e) + notify("plan ready for review") try: k = input("[c]onfirm [e]dit [g]emma revise [q]uit > ").strip().lower() except EOFError: @@ -886,6 +898,7 @@ def main(): page_stage(p, wd, page_path(lib, pj, p["n"]), a.base) log("done") print(f"\ndone: {page_path(lib, pj, 1).parent}") + notify("book done") except (KeyboardInterrupt, EOFError): log("interrupted") sys.exit("\ninterrupted, run again to resume") |
