Skip to content

Commit 101b803

Browse files
feat: add --format json option to canonical command (#117)
* feat(cli): add --format json option to canonical command 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) * fix(cli): serialize canonical durations as seconds
1 parent 2c88b87 commit 101b803

1 file changed

Lines changed: 40 additions & 19 deletions

File tree

aw_client/cli.py

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,20 @@ def print_top(events: List[Event], key=lambda e: e.data, title="Events", n=10):
236236
@click.option("--cache", is_flag=True)
237237
@click.option("--start", default=now - td1day, type=click.DateTime())
238238
@click.option("--stop", default=now + td1yr, type=click.DateTime())
239+
@click.option(
240+
"--format",
241+
type=click.Choice(["table", "json"]),
242+
default="table",
243+
help="Output format (table or json)",
244+
)
239245
@click.pass_obj
240246
def canonical(
241247
obj: _Context,
242248
hostname: str,
243249
cache: bool,
244250
start: datetime,
245251
stop: datetime,
252+
format: str,
246253
name: Optional[str] = None,
247254
):
248255
logger.info(f"Querying between {start} and {stop}")
@@ -270,29 +277,43 @@ def canonical(
270277

271278
# TODO: Print titles, apps, categories, with most time
272279
for period in result:
273-
print()
274280
events = _parse_events(period)
275-
print(f"Showing last 10 out of {len(events)} events:")
276281

277-
print(
278-
tabulate(
279-
[
280-
(
281-
str(e.timestamp).split(".")[0],
282-
str(e.duration).split(".")[0],
283-
f'[{e.data["app"]}] {textwrap.shorten(e.data["title"], 60, placeholder="...")}',
284-
)
285-
for e in events[-10:]
286-
],
287-
headers=["Timestamp", "Duration", "Data"],
282+
if format == "json":
283+
# Output as JSON array with ISO-8601 timestamps
284+
json_events = [
285+
{
286+
"timestamp": e.timestamp.isoformat(),
287+
"duration": e.duration.total_seconds(),
288+
"data": e.data,
289+
}
290+
for e in events
291+
]
292+
print(json.dumps(json_events))
293+
else:
294+
# Table format (original behavior)
295+
print()
296+
print(f"Showing last 10 out of {len(events)} events:")
297+
298+
print(
299+
tabulate(
300+
[
301+
(
302+
str(e.timestamp).split(".")[0],
303+
str(e.duration).split(".")[0],
304+
f'[{e.data["app"]}] {textwrap.shorten(e.data["title"], 60, placeholder="...")}',
305+
)
306+
for e in events[-10:]
307+
],
308+
headers=["Timestamp", "Duration", "Data"],
309+
)
288310
)
289-
)
290311

291-
print()
292-
print(
293-
"Total duration:\t",
294-
timedelta(seconds=sum(e["duration"] for e in period)),
295-
)
312+
print()
313+
print(
314+
"Total duration:\t",
315+
timedelta(seconds=sum(e["duration"] for e in period)),
316+
)
296317

297318

298319
if __name__ == "__main__":

0 commit comments

Comments
 (0)