aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-05 14:04:40 +0200
committerDanilo M. <danix@danix.xyz>2026-10-05 14:04:40 +0200
commited4c217786ccb6f3c106f42209bcf31ac8871753 (patch)
tree1eb76ae67ac22cfcc9763367701003fcb7d0a811
parentf15665670b5f2923bb8ec84929b6bc9b7f29a53a (diff)
downloadfanfictioner-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-xfanfictioner13
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")