Merge pull request #334 from iamitp/codex/last-run-config-state
Preserve clean mode for last run state
This commit is contained in:
@@ -63,14 +63,53 @@ fi
|
|||||||
# Check SETUP_COMPLETE (from file or env)
|
# Check SETUP_COMPLETE (from file or env)
|
||||||
SETUP_COMPLETE="${ENV_SETUP_COMPLETE:-${SETUP_COMPLETE:-}}"
|
SETUP_COMPLETE="${ENV_SETUP_COMPLETE:-${SETUP_COMPLETE:-}}"
|
||||||
|
|
||||||
|
# Compute last-run summary line (if last-run.json exists)
|
||||||
|
if [[ "${LAST30DAYS_CONFIG_DIR+x}" == "x" ]]; then
|
||||||
|
if [[ -n "$LAST30DAYS_CONFIG_DIR" ]]; then
|
||||||
|
LAST_RUN_FILE="$LAST30DAYS_CONFIG_DIR/last-run.json"
|
||||||
|
else
|
||||||
|
LAST_RUN_FILE=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LAST_RUN_FILE="$HOME/.config/last30days/last-run.json"
|
||||||
|
fi
|
||||||
|
LAST_RUN_LINE=""
|
||||||
|
if [[ -n "$LAST_RUN_FILE" && -f "$LAST_RUN_FILE" ]] && command -v python3 &>/dev/null; then
|
||||||
|
LAST_RUN_LINE=$(LAST_RUN_FILE="$LAST_RUN_FILE" python3 - <<'PY' 2>/dev/null || true
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
path = os.environ["LAST_RUN_FILE"]
|
||||||
|
try:
|
||||||
|
with open(path) as fh:
|
||||||
|
d = json.load(fh)
|
||||||
|
topic = (d.get("topic") or "?")[:60]
|
||||||
|
ts = d.get("timestamp", "")
|
||||||
|
dt = datetime.datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||||
|
delta = (datetime.datetime.now(datetime.timezone.utc) - dt).total_seconds()
|
||||||
|
if delta < 60: ago = f"{int(delta)}s ago"
|
||||||
|
elif delta < 3600: ago = f"{int(delta//60)}m ago"
|
||||||
|
elif delta < 86400: ago = f"{int(delta//3600)}h ago"
|
||||||
|
else: ago = f"{int(delta//86400)}d ago"
|
||||||
|
total = d.get("total", 0)
|
||||||
|
print(f" Last run: \"{topic}\" · {ago} · {total} results")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
PY
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
# If setup has never been run, show welcome message for new users
|
# If setup has never been run, show welcome message for new users
|
||||||
if [[ -z "$SETUP_COMPLETE" && -z "$CONFIG_FILE" && -z "${OPENAI_API_KEY:-}" && -z "${SCRAPECREATORS_API_KEY:-}" && -z "${AUTH_TOKEN:-}" && -z "${XAI_API_KEY:-}" ]]; then
|
if [[ -z "$SETUP_COMPLETE" && -z "$CONFIG_FILE" && -z "${OPENAI_API_KEY:-}" && -z "${SCRAPECREATORS_API_KEY:-}" && -z "${AUTH_TOKEN:-}" && -z "${XAI_API_KEY:-}" ]]; then
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
/last30days: Ready to use. Run /last30days to get started — setup takes 30 seconds.
|
/last30days: Ready to use. Run /last30days to get started — setup takes 30 seconds.
|
||||||
|
Research any topic across Reddit, HN, X, YouTube, Polymarket (last 30 days).
|
||||||
|
|
||||||
Reddit, Hacker News, and Polymarket work out of the box.
|
Reddit, Hacker News, and Polymarket work out of the box.
|
||||||
The setup wizard can unlock X/Twitter, YouTube, and more.
|
The setup wizard can unlock X/Twitter, YouTube, and more.
|
||||||
EOF
|
EOF
|
||||||
|
[[ -n "$LAST_RUN_LINE" ]] && echo "$LAST_RUN_LINE"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -121,9 +160,13 @@ fi
|
|||||||
if [[ -n "$HAS_SCRAPECREATORS" ]]; then
|
if [[ -n "$HAS_SCRAPECREATORS" ]]; then
|
||||||
# Fully configured — compact ready message
|
# Fully configured — compact ready message
|
||||||
echo "/last30days: Ready — ${SOURCE_COUNT} sources active."
|
echo "/last30days: Ready — ${SOURCE_COUNT} sources active."
|
||||||
|
echo " Research any topic across social + market + web sources (last 30 days)."
|
||||||
|
[[ -n "$LAST_RUN_LINE" ]] && echo "$LAST_RUN_LINE"
|
||||||
else
|
else
|
||||||
# Setup done but missing ScrapeCreators — recommend it
|
# Setup done but missing ScrapeCreators — recommend it
|
||||||
echo "/last30days: Ready — ${SOURCE_COUNT} sources active."
|
echo "/last30days: Ready — ${SOURCE_COUNT} sources active."
|
||||||
|
echo " Research any topic across social + market + web sources (last 30 days)."
|
||||||
|
[[ -n "$LAST_RUN_LINE" ]] && echo "$LAST_RUN_LINE"
|
||||||
echo " Tip: Add ScrapeCreators for Reddit comments + TikTok + Instagram."
|
echo " Tip: Add ScrapeCreators for Reddit comments + TikTok + Instagram."
|
||||||
echo " 100 free credits, no credit card — scrapecreators.com"
|
echo " 100 free credits, no credit card — scrapecreators.com"
|
||||||
echo " last30days has no affiliation with any API provider."
|
echo " last30days has no affiliation with any API provider."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import atexit
|
import atexit
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -531,6 +532,24 @@ def _show_runtime_ui(
|
|||||||
progress.show_promo(promo, diag=diag)
|
progress.show_promo(promo, diag=diag)
|
||||||
|
|
||||||
|
|
||||||
|
def _write_last_run(topic: str, report: "schema.Report") -> None:
|
||||||
|
try:
|
||||||
|
if env.CONFIG_DIR is None:
|
||||||
|
return
|
||||||
|
target = env.CONFIG_DIR
|
||||||
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
|
counts = {source: len(items) for source, items in report.items_by_source.items()}
|
||||||
|
payload = {
|
||||||
|
"topic": topic,
|
||||||
|
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||||
|
"sources": counts,
|
||||||
|
"total": sum(counts.values()),
|
||||||
|
}
|
||||||
|
(target / "last-run.json").write_text(json.dumps(payload, indent=2))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
# Use parse_known_args so setup sub-flags (--device-auth, --github,
|
# Use parse_known_args so setup sub-flags (--device-auth, --github,
|
||||||
@@ -870,6 +889,7 @@ def main() -> int:
|
|||||||
report, progress, diag,
|
report, progress, diag,
|
||||||
suppress_web_promo=bool(external_plan or comp_plan),
|
suppress_web_promo=bool(external_plan or comp_plan),
|
||||||
)
|
)
|
||||||
|
_write_last_run(topic, report)
|
||||||
if args.store:
|
if args.store:
|
||||||
counts = persist_report(report)
|
counts = persist_report(report)
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
LAST30DAYS_SCRIPT = REPO_ROOT / "skills" / "last30days" / "scripts" / "last30days.py"
|
||||||
|
|
||||||
|
|
||||||
|
def run_last30days(topic: str, env: dict[str, str]) -> subprocess.CompletedProcess[str]:
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, str(LAST30DAYS_SCRIPT), topic, "--mock", "--emit=json"],
|
||||||
|
cwd=REPO_ROOT,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LastRunStateTests(unittest.TestCase):
|
||||||
|
def test_empty_config_override_disables_last_run_write(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
home = Path(tmp) / "home"
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["HOME"] = str(home)
|
||||||
|
env["LAST30DAYS_CONFIG_DIR"] = ""
|
||||||
|
|
||||||
|
result = run_last30days("synthetic eval query", env)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertFalse((home / ".config" / "last30days" / "last-run.json").exists())
|
||||||
|
|
||||||
|
def test_custom_config_override_writes_last_run_to_custom_dir(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
config_dir = Path(tmp) / "custom-config"
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["HOME"] = str(Path(tmp) / "home")
|
||||||
|
env["LAST30DAYS_CONFIG_DIR"] = str(config_dir)
|
||||||
|
|
||||||
|
result = run_last30days("custom config query", env)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
payload = json.loads((config_dir / "last-run.json").read_text())
|
||||||
|
self.assertEqual(payload["topic"], "custom config query")
|
||||||
|
self.assertGreaterEqual(payload["total"], 0)
|
||||||
|
|
||||||
|
def test_hook_reads_last_run_from_custom_config_dir(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
config_dir = Path(tmp) / "custom-config"
|
||||||
|
config_dir.mkdir()
|
||||||
|
(config_dir / "last-run.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"topic": "custom hook query",
|
||||||
|
"timestamp": "2026-04-30T00:00:00+00:00",
|
||||||
|
"sources": {"reddit": 2},
|
||||||
|
"total": 2,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["HOME"] = str(Path(tmp) / "home")
|
||||||
|
env["LAST30DAYS_CONFIG_DIR"] = str(config_dir)
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["bash", "hooks/scripts/check-config.sh"],
|
||||||
|
cwd=REPO_ROOT,
|
||||||
|
env=env,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
self.assertIn('Last run: "custom hook query"', result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user