feat(datastore): support --since in stop command, trigger website update from render_views, update docs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user