feat: add show command with short-index cache

Persist last displayed tweet lists and allow opening a tweet by index.

- Add twitter_cli/cache.py: stores a short-index cache (~/.twitter-cli/last_results.json) with a 1h TTL and helpers to resolve index->tweet-id and cache size.
- Update twitter_cli/cli.py: save list results to cache, display a hint, and add `twitter show <N>` command which fetches a tweet by cached index and prints detail/replies (supports --full-text, --json, structured output, and max replies).
- Update README.md and SKILL.md to document the new `show` usage.
- Add .idea/ to .gitignore and bump package version in uv.lock to 0.8.0.

This change makes it easy to open items from the last feed/search without copying IDs.
This commit is contained in:
Pleasurecruise
2026-03-12 22:12:27 +00:00
parent 41d8ad676e
commit 5335516d57
6 changed files with 135 additions and 3 deletions

62
twitter_cli/cache.py Normal file
View File

@@ -0,0 +1,62 @@
"""Short-index cache: persist the last displayed tweet list for quick `show` access."""
from __future__ import annotations
import json
import logging
import time
from pathlib import Path
from typing import List, Optional
from .models import Tweet
logger = logging.getLogger(__name__)
_CACHE_DIR = Path.home() / ".twitter-cli"
_CACHE_FILE = _CACHE_DIR / "last_results.json"
_TTL = 3600 # seconds
def save_tweet_cache(tweets: List[Tweet]) -> None:
"""Persist tweet list so indices can be resolved by `show`."""
try:
_CACHE_DIR.mkdir(parents=True, exist_ok=True)
entries = [
{"index": i + 1, "id": t.id, "author": t.author.screen_name, "text": t.text[:80]}
for i, t in enumerate(tweets)
if t.id
]
payload = {"created_at": time.time(), "tweets": entries}
_CACHE_FILE.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
except OSError as exc:
logger.debug("Failed to write tweet cache: %s", exc)
def _load_cache() -> Optional[List[dict]]:
"""Load and validate the cache file; return tweet entries or None if stale/missing."""
try:
if not _CACHE_FILE.exists():
return None
payload = json.loads(_CACHE_FILE.read_text(encoding="utf-8"))
if time.time() - payload.get("created_at", 0) > _TTL:
return None
return payload.get("tweets", [])
except (OSError, json.JSONDecodeError):
return None
def get_tweet_id_by_index(index: int) -> Optional[str]:
"""Return tweet ID at *index* (1-based), or None if not found."""
entries = _load_cache()
if entries is None:
return None
for entry in entries:
if entry.get("index") == index:
return str(entry["id"])
return None
def get_cache_size() -> int:
"""Return number of tweets in the current cache (0 if unavailable)."""
entries = _load_cache()
return len(entries) if entries is not None else 0