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:**
|
- **Parameters:**
|
||||||
- `kind` (string, required): `'sleep.session'`.
|
- `kind` (string, required): `'sleep.session'`.
|
||||||
- `subject` (string, required): `'mila'`.
|
- `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`
|
### 3. `datastore_get_summary`
|
||||||
Retrieves daily nutrition, sleep, and active status totals computed by DuckDB.
|
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" \
|
--since "15m" \
|
||||||
--payload '{"type": "nap", "location": "bett"}'
|
--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 \
|
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py stop sleep.session \
|
||||||
--subject mila
|
--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
|
# Query daily summary
|
||||||
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py summary --subject moritz
|
uv run /home/node/mop-skills/mop-skills/skills/datastore/scripts/datastore.py summary --subject moritz
|
||||||
|
|
||||||
|
|||||||
@@ -594,10 +594,14 @@ class DatastoreEngine:
|
|||||||
kind: str,
|
kind: str,
|
||||||
subject: str,
|
subject: str,
|
||||||
end_time: Optional[str] = None,
|
end_time: Optional[str] = None,
|
||||||
|
since: Optional[str] = None,
|
||||||
event_id: Optional[str] = None,
|
event_id: Optional[str] = None,
|
||||||
actor: Optional[str] = None,
|
actor: Optional[str] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Finds the open event (end IS NULL) for kind and subject, and closes it."""
|
"""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:
|
if event_id:
|
||||||
row = self.con.execute(
|
row = self.con.execute(
|
||||||
"""
|
"""
|
||||||
@@ -622,13 +626,22 @@ class DatastoreEngine:
|
|||||||
|
|
||||||
resolved_event_id, start_str = row[0], str(row[1])
|
resolved_event_id, start_str = row[0], str(row[1])
|
||||||
now_dt = datetime.datetime.now(datetime.timezone.utc)
|
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)
|
end_dt = datetime.datetime.fromisoformat(end_time)
|
||||||
if end_dt.tzinfo is None:
|
if end_dt.tzinfo is None:
|
||||||
end_dt = end_dt.replace(tzinfo=BERLIN_TZ)
|
end_dt = end_dt.replace(tzinfo=BERLIN_TZ)
|
||||||
else:
|
else:
|
||||||
end_dt = now_dt.astimezone(BERLIN_TZ)
|
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()
|
end_iso = end_dt.isoformat()
|
||||||
now_iso = now_dt.isoformat()
|
now_iso = now_dt.isoformat()
|
||||||
|
|
||||||
@@ -985,6 +998,8 @@ def run_mcp_server():
|
|||||||
"kind": {"type": "string", "description": "e.g. 'sleep.session'"},
|
"kind": {"type": "string", "description": "e.g. 'sleep.session'"},
|
||||||
"subject": {"type": "string", "description": "'mila', 'moritz', 'amelie'"},
|
"subject": {"type": "string", "description": "'mila', 'moritz', 'amelie'"},
|
||||||
"end_time": {"type": "string", "description": "ISO timestamp (optional, defaults to now)"},
|
"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"],
|
"required": ["kind", "subject"],
|
||||||
},
|
},
|
||||||
@@ -1100,6 +1115,8 @@ def run_mcp_server():
|
|||||||
kind=args["kind"],
|
kind=args["kind"],
|
||||||
subject=args["subject"],
|
subject=args["subject"],
|
||||||
end_time=args.get("end_time"),
|
end_time=args.get("end_time"),
|
||||||
|
since=args.get("since"),
|
||||||
|
event_id=args.get("event_id"),
|
||||||
)
|
)
|
||||||
elif name == "datastore_get_summary":
|
elif name == "datastore_get_summary":
|
||||||
data = engine.get_summary(subject=args["subject"], date_str=args.get("date"))
|
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("--kind", dest="kind_flag", help="e.g. sleep.session")
|
||||||
p_stop.add_argument("--subject", required=True, help="mila, moritz")
|
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("--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
|
# Query
|
||||||
p_query = subparsers.add_parser("query", help="Execute read-only SQL 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
|
kind = args.kind or args.kind_flag
|
||||||
if not kind:
|
if not kind:
|
||||||
parser.error("the 'stop' command requires 'kind' (positional or via --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))
|
print(json.dumps(res, indent=2, ensure_ascii=False))
|
||||||
elif args.command == "query":
|
elif args.command == "query":
|
||||||
res = engine.query(args.sql)
|
res = engine.query(args.sql)
|
||||||
|
|||||||
@@ -579,6 +579,20 @@ def main():
|
|||||||
if not args.no_html:
|
if not args.no_html:
|
||||||
render_html_dashboards(engine, html_dir)
|
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user