From f6036c20f325eafe3a76d24dd778c5fc288e4ba8 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 20 Aug 2026 20:54:14 +0000 Subject: [PATCH 1/2] feat(cli): add --format json option to canonical command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add JSON output support to the 'canonical' subcommand to enable machine-readable access to canonical events. This supports the ActivityWatch × AI agent enablement workflow, allowing agents to consume categorized activity data without reimplementing the canonical query. - Add --format option with 'table' (default) and 'json' choices - JSON output emits event array with ISO-8601 timestamps - Preserves existing table format behavior (human-readable) Closes: ActivityWatch/aw-client contribution for agent enablement. Reference: https://github.com/ActivityWatch/aw-client/issues (agent enablement docs) --- aw_client/cli.py | 54 +++++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/aw_client/cli.py b/aw_client/cli.py index c1215ed..a005c2f 100755 --- a/aw_client/cli.py +++ b/aw_client/cli.py @@ -236,6 +236,7 @@ def print_top(events: List[Event], key=lambda e: e.data, title="Events", n=10): @click.option("--cache", is_flag=True) @click.option("--start", default=now - td1day, type=click.DateTime()) @click.option("--stop", default=now + td1yr, type=click.DateTime()) +@click.option("--format", type=click.Choice(["table", "json"]), default="table", help="Output format (table or json)") @click.pass_obj def canonical( obj: _Context, @@ -243,6 +244,7 @@ def canonical( cache: bool, start: datetime, stop: datetime, + format: str, name: Optional[str] = None, ): logger.info(f"Querying between {start} and {stop}") @@ -270,29 +272,43 @@ def canonical( # TODO: Print titles, apps, categories, with most time for period in result: - print() events = _parse_events(period) - print(f"Showing last 10 out of {len(events)} events:") - print( - tabulate( - [ - ( - str(e.timestamp).split(".")[0], - str(e.duration).split(".")[0], - f'[{e.data["app"]}] {textwrap.shorten(e.data["title"], 60, placeholder="...")}', - ) - for e in events[-10:] - ], - headers=["Timestamp", "Duration", "Data"], + if format == "json": + # Output as JSON array with ISO-8601 timestamps + json_events = [ + { + "timestamp": e.timestamp.isoformat(), + "duration": e.duration, + "data": e.data, + } + for e in events + ] + print(json.dumps(json_events)) + else: + # Table format (original behavior) + print() + print(f"Showing last 10 out of {len(events)} events:") + + print( + tabulate( + [ + ( + str(e.timestamp).split(".")[0], + str(e.duration).split(".")[0], + f'[{e.data["app"]}] {textwrap.shorten(e.data["title"], 60, placeholder="...")}', + ) + for e in events[-10:] + ], + headers=["Timestamp", "Duration", "Data"], + ) ) - ) - print() - print( - "Total duration:\t", - timedelta(seconds=sum(e["duration"] for e in period)), - ) + print() + print( + "Total duration:\t", + timedelta(seconds=sum(e["duration"] for e in period)), + ) if __name__ == "__main__": From fb7cee707b919b84b85d06e2b51404cefa53a001 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 20 Aug 2026 21:23:16 +0000 Subject: [PATCH 2/2] fix(cli): serialize canonical durations as seconds --- aw_client/cli.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aw_client/cli.py b/aw_client/cli.py index a005c2f..724bbc7 100755 --- a/aw_client/cli.py +++ b/aw_client/cli.py @@ -236,7 +236,12 @@ def print_top(events: List[Event], key=lambda e: e.data, title="Events", n=10): @click.option("--cache", is_flag=True) @click.option("--start", default=now - td1day, type=click.DateTime()) @click.option("--stop", default=now + td1yr, type=click.DateTime()) -@click.option("--format", type=click.Choice(["table", "json"]), default="table", help="Output format (table or json)") +@click.option( + "--format", + type=click.Choice(["table", "json"]), + default="table", + help="Output format (table or json)", +) @click.pass_obj def canonical( obj: _Context, @@ -279,7 +284,7 @@ def canonical( json_events = [ { "timestamp": e.timestamp.isoformat(), - "duration": e.duration, + "duration": e.duration.total_seconds(), "data": e.data, } for e in events