diff options
Diffstat (limited to '.ai/scripts/inbox-send.py')
| -rwxr-xr-x | .ai/scripts/inbox-send.py | 44 |
1 files changed, 39 insertions, 5 deletions
diff --git a/.ai/scripts/inbox-send.py b/.ai/scripts/inbox-send.py index 5373bd4..1ebb636 100755 --- a/.ai/scripts/inbox-send.py +++ b/.ai/scripts/inbox-send.py @@ -136,8 +136,21 @@ def slugify_filename(stem: str, max_length: int = MAX_SLUG_LENGTH) -> str: return truncated.strip("-._") +def display_name(path: Path) -> str: + """The name a project is referred to by — its basename with dots stripped. + + Dotted directories (`.emacs.d`, `.dotfiles`) are awkward to name in + conversation, so they're addressed dot-stripped: `emacsd`, `dotfiles`. + """ + return path.name.replace(".", "") + + def find_target(target_name: str, projects: list[Path]) -> Path | None: - """Resolve `target_name` against the project list (basename or numeric index).""" + """Resolve `target_name` against the project list (basename or numeric index). + + An exact basename match wins. Failing that, a dot-stripped alias matches — + so `emacsd` resolves `.emacs.d` and `dotfiles` resolves `.dotfiles`. + """ if target_name.isdigit(): idx = int(target_name) - 1 if 0 <= idx < len(projects): @@ -146,6 +159,10 @@ def find_target(target_name: str, projects: list[Path]) -> Path | None: for p in projects: if p.name == target_name: return p + norm = target_name.replace(".", "") + for p in projects: + if display_name(p) == norm: + return p return None @@ -160,6 +177,23 @@ def build_text_org(message: str, source_name: str, timestamp: str) -> str: ) +def uniquify(dest: Path) -> Path: + """Return dest, or dest with a -2/-3/... stem suffix when it already exists. + + Two sends in the same minute whose text starts with the same phrase + derive identical filenames, and the second silently overwrote the + first (a message was lost this way, 2026-07-02). Never overwrite. + """ + if not dest.exists(): + return dest + n = 2 + while True: + candidate = dest.with_name(f"{dest.stem}-{n}{dest.suffix}") + if not candidate.exists(): + return candidate + n += 1 + + def send_text( target_inbox: Path, message: str, @@ -174,7 +208,7 @@ def send_text( if not slug: raise ValueError(f"could not derive a slug from text: {message!r}") filename = f"{now.strftime(TS_FILENAME_FMT)}-from-{source_name}-{slug}.org" - dest = target_inbox / filename + dest = uniquify(target_inbox / filename) dest.write_text(build_text_org(message, source_name, now.strftime(TS_DOC_FMT))) return dest @@ -194,7 +228,7 @@ def send_file( raise ValueError(f"could not derive a slug from file: {src_path}") ext = src_path.suffix filename = f"{now.strftime(TS_FILENAME_FMT)}-from-{source_name}-{slug}{ext}" - dest = target_inbox / filename + dest = uniquify(target_inbox / filename) shutil.copy2(src_path, dest) return dest @@ -206,9 +240,9 @@ def print_project_list(projects: list[Path], current: Path | None) -> None: print("No projects (.ai/ + inbox/) found under the configured roots.") return print(f"Available .ai projects ({len(others)}):") - width = max(len(p.name) for p in others) + width = max(len(display_name(p)) for p in others) for i, p in enumerate(others, 1): - print(f" {i}. {p.name:<{width}} {p}") + print(f" {i}. {display_name(p):<{width}} {p}") def main() -> int: |
