feat(datastore): support --since in stop command, trigger website update from render_views, update docs

This commit is contained in:
Moritz Graf
2026-10-11 15:07:35 +02:00
parent 967909c3a7
commit 3abfa18182
3 changed files with 55 additions and 5 deletions
+14 -2
View File
@@ -60,7 +60,9 @@ Closes the currently active open interval event (e.g. when Mila wakes up).
- **Parameters:**
- `kind` (string, required): `'sleep.session'`.
- `subject` (string, required): `'mila'`.
- `end_time` (string, optional): ISO timestamp (defaults to current time).
- `end_time` (string, optional): ISO timestamp (e.g. `'2026-10-11T11:40:00+02:00'`). Defaults to current time if omitted.
- `since` (string, optional): Relative wake time offset backwards from now (e.g. `'10m'`, `'15m'`).
- `event_id` (string, optional): Specific open event ID to close.
### 3. `datastore_get_summary`
Retrieves daily nutrition, sleep, and active status totals computed by DuckDB.
@@ -113,10 +115,20 @@ uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py ad
--since "15m" \
--payload '{"type": "nap", "location": "bett"}'
# Stop Mila's nap (closes open interval)
# Stop Mila's nap (closes open interval at current time)
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py stop sleep.session \
--subject mila
# Stop Mila's nap retroactively with specific wake time
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py stop sleep.session \
--subject mila \
--end "2026-10-11T11:40:00+02:00"
# Stop Mila's nap retroactively with relative wake time (e.g. woke up 15 min ago)
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py stop sleep.session \
--subject mila \
--since "15m"
# Query daily summary
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py summary --subject moritz
+27 -3
View File
@@ -594,10 +594,14 @@ class DatastoreEngine:
kind: str,
subject: str,
end_time: Optional[str] = None,
since: Optional[str] = None,
event_id: Optional[str] = None,
actor: Optional[str] = None,
) -> Dict[str, Any]:
"""Finds the open event (end IS NULL) for kind and subject, and closes it."""
if since and end_time:
raise ValueError("Cannot specify both 'since' and 'end_time'. Use 'since' for relative times or 'end_time' for absolute times.")
if event_id:
row = self.con.execute(
"""
@@ -622,13 +626,22 @@ class DatastoreEngine:
resolved_event_id, start_str = row[0], str(row[1])
now_dt = datetime.datetime.now(datetime.timezone.utc)
if end_time:
if since:
delta = parse_duration(since)
end_dt = (now_dt - delta).astimezone(BERLIN_TZ)
elif end_time:
end_dt = datetime.datetime.fromisoformat(end_time)
if end_dt.tzinfo is None:
end_dt = end_dt.replace(tzinfo=BERLIN_TZ)
else:
end_dt = now_dt.astimezone(BERLIN_TZ)
start_dt = datetime.datetime.fromisoformat(start_str.replace("Z", "+00:00"))
if start_dt.tzinfo is None:
start_dt = start_dt.replace(tzinfo=BERLIN_TZ)
if end_dt < start_dt:
raise ValueError(f"End time {end_dt.isoformat()} cannot be earlier than start time {start_dt.isoformat()}")
end_iso = end_dt.isoformat()
now_iso = now_dt.isoformat()
@@ -985,6 +998,8 @@ def run_mcp_server():
"kind": {"type": "string", "description": "e.g. 'sleep.session'"},
"subject": {"type": "string", "description": "'mila', 'moritz', 'amelie'"},
"end_time": {"type": "string", "description": "ISO timestamp (optional, defaults to now)"},
"since": {"type": "string", "description": "Relative end offset backwards from now (e.g. '5m', '15m')"},
"event_id": {"type": "string", "description": "Specific open event ID to close (optional)"},
},
"required": ["kind", "subject"],
},
@@ -1100,6 +1115,8 @@ def run_mcp_server():
kind=args["kind"],
subject=args["subject"],
end_time=args.get("end_time"),
since=args.get("since"),
event_id=args.get("event_id"),
)
elif name == "datastore_get_summary":
data = engine.get_summary(subject=args["subject"], date_str=args.get("date"))
@@ -1177,7 +1194,8 @@ def run_cli():
p_stop.add_argument("--kind", dest="kind_flag", help="e.g. sleep.session")
p_stop.add_argument("--subject", required=True, help="mila, moritz")
p_stop.add_argument("--id", dest="event_id", help="Specific event ID to close")
p_stop.add_argument("--end", help="ISO end time (defaults to now)")
p_stop.add_argument("--end", dest="end_time", help="ISO end time (defaults to now)")
p_stop.add_argument("--since", help="Relative end offset backwards from now (e.g. '5m', '15m')")
# Query
p_query = subparsers.add_parser("query", help="Execute read-only SQL query")
@@ -1226,7 +1244,13 @@ def run_cli():
kind = args.kind or args.kind_flag
if not kind:
parser.error("the 'stop' command requires 'kind' (positional or via --kind)")
res = engine.stop_event(kind=kind, subject=args.subject, end_time=args.end, event_id=getattr(args, "event_id", None))
res = engine.stop_event(
kind=kind,
subject=args.subject,
end_time=getattr(args, "end_time", None),
since=getattr(args, "since", None),
event_id=getattr(args, "event_id", None),
)
print(json.dumps(res, indent=2, ensure_ascii=False))
elif args.command == "query":
res = engine.query(args.sql)
+14
View File
@@ -579,6 +579,20 @@ def main():
if not args.no_html:
render_html_dashboards(engine, html_dir)
# Automatically trigger update_website.py for live publishing to hosting
try:
update_script = Path(__file__).resolve().parent / "update_website.py"
if update_script.exists():
import subprocess, shutil
uv_bin = shutil.which("uv") or "/home/node/.openclaw/bin/uv"
if Path(uv_bin).is_file():
cmd = [str(uv_bin), "run", str(update_script)]
else:
cmd = [sys.executable, str(update_script)]
subprocess.run(cmd, check=False)
except Exception as e:
print(f"Warning: could not trigger update_website.py from render_views: {e}")
if __name__ == "__main__":
main()