aw-watcher-agent 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aw_watcher_agent-0.2.0/.gitignore +43 -0
- aw_watcher_agent-0.2.0/PKG-INFO +188 -0
- aw_watcher_agent-0.2.0/README.md +167 -0
- aw_watcher_agent-0.2.0/pyproject.toml +43 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/__init__.py +6 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/cc_tailer.py +262 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/cli.py +287 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/client.py +150 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/core.py +112 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/py.typed +0 -0
- aw_watcher_agent-0.2.0/src/aw_watcher_agent/tailer.py +256 -0
- aw_watcher_agent-0.2.0/tests/test_aw_watcher_agent.py +657 -0
- aw_watcher_agent-0.2.0/tests/test_cc_tailer.py +384 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
*.pyc
|
|
2
|
+
__pycache__
|
|
3
|
+
*.egg-info
|
|
4
|
+
.mypy_cache
|
|
5
|
+
.pytest_cache
|
|
6
|
+
.ruff_cache
|
|
7
|
+
|
|
8
|
+
# Python build artifacts
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
|
|
12
|
+
# Secrets - never commit
|
|
13
|
+
scripts/discord/logs/*
|
|
14
|
+
scripts/*/.env
|
|
15
|
+
scripts/*/.oauth-state
|
|
16
|
+
scripts/*/.tokens.json
|
|
17
|
+
scripts/.env
|
|
18
|
+
*.oauth-state
|
|
19
|
+
*.tokens.json
|
|
20
|
+
# Per-agent identity allowlist written by dotfiles/install.sh. It carries a
|
|
21
|
+
# machine-specific git identity and must never be committed from a checkout
|
|
22
|
+
# whose ~/.config is symlinked into this tree.
|
|
23
|
+
dotfiles/.config/git/allowed-identities.conf
|
|
24
|
+
|
|
25
|
+
.venv/
|
|
26
|
+
venv/
|
|
27
|
+
.gptme/
|
|
28
|
+
|
|
29
|
+
# Community plugins index (generated artifact — do not commit snapshots)
|
|
30
|
+
state/community_plugins.json
|
|
31
|
+
docs/community_plugins.json
|
|
32
|
+
|
|
33
|
+
# Task lock files (runtime state)
|
|
34
|
+
state/locks/
|
|
35
|
+
|
|
36
|
+
# Session state files (runtime state)
|
|
37
|
+
state/sessions/
|
|
38
|
+
|
|
39
|
+
# Stale directory from tasks->gptodo rename (PR #171)
|
|
40
|
+
packages/tasks/
|
|
41
|
+
|
|
42
|
+
# Dashboard build output
|
|
43
|
+
_site/
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: aw-watcher-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: ActivityWatch watcher for AI coding assistants (gptme, Claude Code, Codex)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: activitywatch,ai-agents,aw-watcher,gptme,time-tracking
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# aw-watcher-agent
|
|
23
|
+
|
|
24
|
+
An [ActivityWatch](https://activitywatch.net) watcher for **AI coding
|
|
25
|
+
assistants** (gptme, Claude Code, Codex). It logs agent sessions and per-tool
|
|
26
|
+
activity to your **local** aw-server, so AI work shows up in the aw-webui
|
|
27
|
+
Timeline alongside window/AFK data instead of looking like idle time.
|
|
28
|
+
|
|
29
|
+
**Status:** alpha (`0.2.0`). Session tracking and the Codex log-tailer work and
|
|
30
|
+
are dogfooded against a local aw-server, as is the Claude Code log-tailer. A
|
|
31
|
+
native gptme plugin hook for per-tool events and an aw-webui "AI work" view are
|
|
32
|
+
not built yet.
|
|
33
|
+
|
|
34
|
+
This is the controller use case behind
|
|
35
|
+
[ActivityWatch/activitywatch#1215](https://github.com/ActivityWatch/activitywatch/issues/1215).
|
|
36
|
+
|
|
37
|
+
## Design
|
|
38
|
+
|
|
39
|
+
- **Local-only, privacy-first.** Writes go only to your own aw-server
|
|
40
|
+
(default `http://127.0.0.1:5600`). No hosted aggregation and no transcripts —
|
|
41
|
+
only coarse metadata (harness, model, category, tool name, status, counts).
|
|
42
|
+
- **No dependencies.** A small vendored stdlib REST client instead of
|
|
43
|
+
`aw-client` / `aw-core`, so it is light enough to call from a lifecycle hook.
|
|
44
|
+
- **One clean Timeline block per session.** `emit-start` posts a zero-duration
|
|
45
|
+
placeholder; `emit-end` deletes it and posts a single event with the full
|
|
46
|
+
duration plus `outcome`. If the session crashes before `emit-end`, the
|
|
47
|
+
placeholder still marks that it started.
|
|
48
|
+
- **Never breaks the session it observes.** Errors are reported on stderr and
|
|
49
|
+
the CLI exits 0, unless you pass `--strict`.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pipx install aw-watcher-agent # or: pip install aw-watcher-agent / uv tool install aw-watcher-agent
|
|
55
|
+
# latest unreleased master:
|
|
56
|
+
pip install "git+https://github.com/gptme/gptme-contrib#subdirectory=packages/aw-watcher-agent"
|
|
57
|
+
# or, from a gptme-contrib checkout:
|
|
58
|
+
pip install -e packages/aw-watcher-agent
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This installs the `aw-watcher-agent` command. You need a running aw-server
|
|
62
|
+
(ActivityWatch's default on port 5600).
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### Sessions
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Create the session bucket (idempotent; emit-start also does this)
|
|
70
|
+
aw-watcher-agent ensure-bucket
|
|
71
|
+
|
|
72
|
+
# At session start
|
|
73
|
+
aw-watcher-agent emit-start \
|
|
74
|
+
--harness claude-code --model claude-opus-4-7 \
|
|
75
|
+
--category code --session-id 8531 --trigger autonomous --workspace myagent
|
|
76
|
+
|
|
77
|
+
# At session end (reads the saved start state, records duration + outcome)
|
|
78
|
+
aw-watcher-agent emit-end --session-id 8531 --outcome productive
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Events land in the bucket `aw-watcher-agent_<hostname>` (type
|
|
82
|
+
`app.agent.session`), which appears in aw-webui automatically.
|
|
83
|
+
|
|
84
|
+
Session flags (shared by `emit-start`, `emit-end` and `emit-activity`):
|
|
85
|
+
`--harness`, `--model`, `--category`, `--session-id`, `--trigger`,
|
|
86
|
+
`--workspace`, `--hostname` (default: the machine hostname) and `--server`
|
|
87
|
+
(aw-server base URL). `emit-end` additionally takes `--outcome` and
|
|
88
|
+
`--duration` (seconds; used only when no start state was recorded). Values
|
|
89
|
+
passed to `emit-end` override the ones saved at start.
|
|
90
|
+
|
|
91
|
+
Start state is kept per session id under
|
|
92
|
+
`$XDG_STATE_HOME/aw-watcher-agent/` (default `~/.local/state/aw-watcher-agent/`).
|
|
93
|
+
|
|
94
|
+
### Claude Code without hooks (log-tailer)
|
|
95
|
+
|
|
96
|
+
`tail-claude-code` reads the transcripts Claude Code already writes under
|
|
97
|
+
`~/.claude/projects/` (or `$CLAUDE_CONFIG_DIR/projects`) and posts one
|
|
98
|
+
session event per working block, so Claude Code work shows up on the Timeline
|
|
99
|
+
with no harness configuration:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
aw-watcher-agent tail-claude-code # every transcript
|
|
103
|
+
aw-watcher-agent tail-claude-code --gap 600 # split blocks on 10 min idle
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Only `timestamp`, `sessionId` and `cwd` are read; prompts, replies and tool
|
|
107
|
+
payloads are never touched. A session is split into blocks on idle gaps
|
|
108
|
+
longer than `--gap` (default 15 min), so a session left open overnight is not
|
|
109
|
+
counted as agent time. The trailing block is held back until it has been idle
|
|
110
|
+
for `--gap`, and a per-transcript cursor records each successful post. Overlapping
|
|
111
|
+
invocations skip transcripts already being processed. Cursor writes are atomic;
|
|
112
|
+
retrying a failed later post does not replay the checkpointed prefix. Once blocks
|
|
113
|
+
have been emitted, changing `--gap` for that transcript is rejected rather than
|
|
114
|
+
posting overlapping representations. A digest of the checkpointed event metadata
|
|
115
|
+
also rejects historical inserts, rewrites or truncation that change already emitted
|
|
116
|
+
blocks; it does not silently skip or replay them. Only a missing cursor starts a
|
|
117
|
+
new import: unreadable, corrupt or older count-only cursors fail closed. Preserve
|
|
118
|
+
such a cursor and reconcile the destination events before resetting it, since
|
|
119
|
+
removing it reimports the transcript. A failed transcript is reported on stderr
|
|
120
|
+
without preventing other transcripts from being processed; `--strict` returns
|
|
121
|
+
nonzero after the scan if any failed. This is not transactional exactly-once
|
|
122
|
+
delivery: a crash after the server accepts a post but before its checkpoint can
|
|
123
|
+
still duplicate that last event on retry. Events land in the same session bucket
|
|
124
|
+
as the hooks below, with `harness=claude-code` and `workspace` set to the project
|
|
125
|
+
directory. Use either the tailer or lifecycle hooks for a session, not both.
|
|
126
|
+
|
|
127
|
+
### Wiring into Claude Code (or any harness with lifecycle hooks)
|
|
128
|
+
|
|
129
|
+
Point your harness's session-start / session-end hooks (for Claude Code:
|
|
130
|
+
`SessionStart` / `SessionEnd` in `settings.json`) at `emit-start` / `emit-end`,
|
|
131
|
+
passing the same `--session-id` to both. Do not use Claude Code's `Stop` hook for
|
|
132
|
+
`emit-end`: it fires after every response, so the first turn closes the session
|
|
133
|
+
and each later turn posts an empty 0s event. Keep the `SessionEnd` hook
|
|
134
|
+
synchronous (not `async`) so the post finishes before the process exits.
|
|
135
|
+
|
|
136
|
+
### Per-tool activity
|
|
137
|
+
|
|
138
|
+
Tool calls go to a sibling bucket, `aw-watcher-agent-activity_<hostname>`
|
|
139
|
+
(type `app.agent.activity`). Adjacent calls with the same tool and status within
|
|
140
|
+
`--pulsetime` seconds (default 5) merge into one Timeline block.
|
|
141
|
+
|
|
142
|
+
Emit a single tool event yourself, e.g. from a post-tool hook:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
aw-watcher-agent emit-activity --harness claude-code --session-id 8531 \
|
|
146
|
+
--tool shell --status success --duration-ms 1200
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Per-tool activity from Codex (log-tailer)
|
|
150
|
+
|
|
151
|
+
Codex can't host an in-process hook, so `tail-codex` reads its rollout
|
|
152
|
+
transcripts (`~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`), pairs each
|
|
153
|
+
`function_call` with its `function_call_output`, derives a coarse
|
|
154
|
+
`success` / `error` / `completed` status, and emits one activity event per call:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Process the most recent rollout transcript
|
|
158
|
+
aw-watcher-agent tail-codex
|
|
159
|
+
|
|
160
|
+
# Or a specific transcript, with a custom merge window
|
|
161
|
+
aw-watcher-agent tail-codex --file ~/.codex/sessions/2026/05/29/rollout-...jsonl --pulsetime 5
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A cursor per transcript (in the same state directory) records what was already
|
|
165
|
+
emitted, so it is safe to run `tail-codex` repeatedly from a timer or after
|
|
166
|
+
each Codex run.
|
|
167
|
+
|
|
168
|
+
## Command reference
|
|
169
|
+
|
|
170
|
+
| Command | Purpose |
|
|
171
|
+
|---------|---------|
|
|
172
|
+
| `ensure-bucket` | Create the session bucket if missing |
|
|
173
|
+
| `emit-start` | Record session start (zero-duration placeholder) |
|
|
174
|
+
| `emit-end` | Replace the placeholder with one event carrying duration + `--outcome` |
|
|
175
|
+
| `emit-activity` | Record one tool-activity heartbeat (`--tool` required) |
|
|
176
|
+
| `tail-codex` | Emit per-tool activity from a Codex rollout transcript |
|
|
177
|
+
| `tail-claude-code` | Emit session working blocks from Claude Code transcripts (no hooks) |
|
|
178
|
+
|
|
179
|
+
Global flag: `--strict` (exit non-zero on errors). Run
|
|
180
|
+
`aw-watcher-agent <command> --help` for all options.
|
|
181
|
+
|
|
182
|
+
## Related
|
|
183
|
+
|
|
184
|
+
- [gptme-sessions](../gptme-sessions/README.md) — session tracking and
|
|
185
|
+
analytics on the agent side (this watcher puts the same sessions on your
|
|
186
|
+
ActivityWatch timeline).
|
|
187
|
+
- [gptme-activity-summary](../gptme-activity-summary/README.md) — summarizes
|
|
188
|
+
agent activity, including ActivityWatch data.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# aw-watcher-agent
|
|
2
|
+
|
|
3
|
+
An [ActivityWatch](https://activitywatch.net) watcher for **AI coding
|
|
4
|
+
assistants** (gptme, Claude Code, Codex). It logs agent sessions and per-tool
|
|
5
|
+
activity to your **local** aw-server, so AI work shows up in the aw-webui
|
|
6
|
+
Timeline alongside window/AFK data instead of looking like idle time.
|
|
7
|
+
|
|
8
|
+
**Status:** alpha (`0.2.0`). Session tracking and the Codex log-tailer work and
|
|
9
|
+
are dogfooded against a local aw-server, as is the Claude Code log-tailer. A
|
|
10
|
+
native gptme plugin hook for per-tool events and an aw-webui "AI work" view are
|
|
11
|
+
not built yet.
|
|
12
|
+
|
|
13
|
+
This is the controller use case behind
|
|
14
|
+
[ActivityWatch/activitywatch#1215](https://github.com/ActivityWatch/activitywatch/issues/1215).
|
|
15
|
+
|
|
16
|
+
## Design
|
|
17
|
+
|
|
18
|
+
- **Local-only, privacy-first.** Writes go only to your own aw-server
|
|
19
|
+
(default `http://127.0.0.1:5600`). No hosted aggregation and no transcripts —
|
|
20
|
+
only coarse metadata (harness, model, category, tool name, status, counts).
|
|
21
|
+
- **No dependencies.** A small vendored stdlib REST client instead of
|
|
22
|
+
`aw-client` / `aw-core`, so it is light enough to call from a lifecycle hook.
|
|
23
|
+
- **One clean Timeline block per session.** `emit-start` posts a zero-duration
|
|
24
|
+
placeholder; `emit-end` deletes it and posts a single event with the full
|
|
25
|
+
duration plus `outcome`. If the session crashes before `emit-end`, the
|
|
26
|
+
placeholder still marks that it started.
|
|
27
|
+
- **Never breaks the session it observes.** Errors are reported on stderr and
|
|
28
|
+
the CLI exits 0, unless you pass `--strict`.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install aw-watcher-agent # or: pip install aw-watcher-agent / uv tool install aw-watcher-agent
|
|
34
|
+
# latest unreleased master:
|
|
35
|
+
pip install "git+https://github.com/gptme/gptme-contrib#subdirectory=packages/aw-watcher-agent"
|
|
36
|
+
# or, from a gptme-contrib checkout:
|
|
37
|
+
pip install -e packages/aw-watcher-agent
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This installs the `aw-watcher-agent` command. You need a running aw-server
|
|
41
|
+
(ActivityWatch's default on port 5600).
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Sessions
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Create the session bucket (idempotent; emit-start also does this)
|
|
49
|
+
aw-watcher-agent ensure-bucket
|
|
50
|
+
|
|
51
|
+
# At session start
|
|
52
|
+
aw-watcher-agent emit-start \
|
|
53
|
+
--harness claude-code --model claude-opus-4-7 \
|
|
54
|
+
--category code --session-id 8531 --trigger autonomous --workspace myagent
|
|
55
|
+
|
|
56
|
+
# At session end (reads the saved start state, records duration + outcome)
|
|
57
|
+
aw-watcher-agent emit-end --session-id 8531 --outcome productive
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Events land in the bucket `aw-watcher-agent_<hostname>` (type
|
|
61
|
+
`app.agent.session`), which appears in aw-webui automatically.
|
|
62
|
+
|
|
63
|
+
Session flags (shared by `emit-start`, `emit-end` and `emit-activity`):
|
|
64
|
+
`--harness`, `--model`, `--category`, `--session-id`, `--trigger`,
|
|
65
|
+
`--workspace`, `--hostname` (default: the machine hostname) and `--server`
|
|
66
|
+
(aw-server base URL). `emit-end` additionally takes `--outcome` and
|
|
67
|
+
`--duration` (seconds; used only when no start state was recorded). Values
|
|
68
|
+
passed to `emit-end` override the ones saved at start.
|
|
69
|
+
|
|
70
|
+
Start state is kept per session id under
|
|
71
|
+
`$XDG_STATE_HOME/aw-watcher-agent/` (default `~/.local/state/aw-watcher-agent/`).
|
|
72
|
+
|
|
73
|
+
### Claude Code without hooks (log-tailer)
|
|
74
|
+
|
|
75
|
+
`tail-claude-code` reads the transcripts Claude Code already writes under
|
|
76
|
+
`~/.claude/projects/` (or `$CLAUDE_CONFIG_DIR/projects`) and posts one
|
|
77
|
+
session event per working block, so Claude Code work shows up on the Timeline
|
|
78
|
+
with no harness configuration:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
aw-watcher-agent tail-claude-code # every transcript
|
|
82
|
+
aw-watcher-agent tail-claude-code --gap 600 # split blocks on 10 min idle
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Only `timestamp`, `sessionId` and `cwd` are read; prompts, replies and tool
|
|
86
|
+
payloads are never touched. A session is split into blocks on idle gaps
|
|
87
|
+
longer than `--gap` (default 15 min), so a session left open overnight is not
|
|
88
|
+
counted as agent time. The trailing block is held back until it has been idle
|
|
89
|
+
for `--gap`, and a per-transcript cursor records each successful post. Overlapping
|
|
90
|
+
invocations skip transcripts already being processed. Cursor writes are atomic;
|
|
91
|
+
retrying a failed later post does not replay the checkpointed prefix. Once blocks
|
|
92
|
+
have been emitted, changing `--gap` for that transcript is rejected rather than
|
|
93
|
+
posting overlapping representations. A digest of the checkpointed event metadata
|
|
94
|
+
also rejects historical inserts, rewrites or truncation that change already emitted
|
|
95
|
+
blocks; it does not silently skip or replay them. Only a missing cursor starts a
|
|
96
|
+
new import: unreadable, corrupt or older count-only cursors fail closed. Preserve
|
|
97
|
+
such a cursor and reconcile the destination events before resetting it, since
|
|
98
|
+
removing it reimports the transcript. A failed transcript is reported on stderr
|
|
99
|
+
without preventing other transcripts from being processed; `--strict` returns
|
|
100
|
+
nonzero after the scan if any failed. This is not transactional exactly-once
|
|
101
|
+
delivery: a crash after the server accepts a post but before its checkpoint can
|
|
102
|
+
still duplicate that last event on retry. Events land in the same session bucket
|
|
103
|
+
as the hooks below, with `harness=claude-code` and `workspace` set to the project
|
|
104
|
+
directory. Use either the tailer or lifecycle hooks for a session, not both.
|
|
105
|
+
|
|
106
|
+
### Wiring into Claude Code (or any harness with lifecycle hooks)
|
|
107
|
+
|
|
108
|
+
Point your harness's session-start / session-end hooks (for Claude Code:
|
|
109
|
+
`SessionStart` / `SessionEnd` in `settings.json`) at `emit-start` / `emit-end`,
|
|
110
|
+
passing the same `--session-id` to both. Do not use Claude Code's `Stop` hook for
|
|
111
|
+
`emit-end`: it fires after every response, so the first turn closes the session
|
|
112
|
+
and each later turn posts an empty 0s event. Keep the `SessionEnd` hook
|
|
113
|
+
synchronous (not `async`) so the post finishes before the process exits.
|
|
114
|
+
|
|
115
|
+
### Per-tool activity
|
|
116
|
+
|
|
117
|
+
Tool calls go to a sibling bucket, `aw-watcher-agent-activity_<hostname>`
|
|
118
|
+
(type `app.agent.activity`). Adjacent calls with the same tool and status within
|
|
119
|
+
`--pulsetime` seconds (default 5) merge into one Timeline block.
|
|
120
|
+
|
|
121
|
+
Emit a single tool event yourself, e.g. from a post-tool hook:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
aw-watcher-agent emit-activity --harness claude-code --session-id 8531 \
|
|
125
|
+
--tool shell --status success --duration-ms 1200
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Per-tool activity from Codex (log-tailer)
|
|
129
|
+
|
|
130
|
+
Codex can't host an in-process hook, so `tail-codex` reads its rollout
|
|
131
|
+
transcripts (`~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl`), pairs each
|
|
132
|
+
`function_call` with its `function_call_output`, derives a coarse
|
|
133
|
+
`success` / `error` / `completed` status, and emits one activity event per call:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Process the most recent rollout transcript
|
|
137
|
+
aw-watcher-agent tail-codex
|
|
138
|
+
|
|
139
|
+
# Or a specific transcript, with a custom merge window
|
|
140
|
+
aw-watcher-agent tail-codex --file ~/.codex/sessions/2026/05/29/rollout-...jsonl --pulsetime 5
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A cursor per transcript (in the same state directory) records what was already
|
|
144
|
+
emitted, so it is safe to run `tail-codex` repeatedly from a timer or after
|
|
145
|
+
each Codex run.
|
|
146
|
+
|
|
147
|
+
## Command reference
|
|
148
|
+
|
|
149
|
+
| Command | Purpose |
|
|
150
|
+
|---------|---------|
|
|
151
|
+
| `ensure-bucket` | Create the session bucket if missing |
|
|
152
|
+
| `emit-start` | Record session start (zero-duration placeholder) |
|
|
153
|
+
| `emit-end` | Replace the placeholder with one event carrying duration + `--outcome` |
|
|
154
|
+
| `emit-activity` | Record one tool-activity heartbeat (`--tool` required) |
|
|
155
|
+
| `tail-codex` | Emit per-tool activity from a Codex rollout transcript |
|
|
156
|
+
| `tail-claude-code` | Emit session working blocks from Claude Code transcripts (no hooks) |
|
|
157
|
+
|
|
158
|
+
Global flag: `--strict` (exit non-zero on errors). Run
|
|
159
|
+
`aw-watcher-agent <command> --help` for all options.
|
|
160
|
+
|
|
161
|
+
## Related
|
|
162
|
+
|
|
163
|
+
- [gptme-sessions](../gptme-sessions/README.md) — session tracking and
|
|
164
|
+
analytics on the agent side (this watcher puts the same sessions on your
|
|
165
|
+
ActivityWatch timeline).
|
|
166
|
+
- [gptme-activity-summary](../gptme-activity-summary/README.md) — summarizes
|
|
167
|
+
agent activity, including ActivityWatch data.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "aw-watcher-agent"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "ActivityWatch watcher for AI coding assistants (gptme, Claude Code, Codex)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
keywords = ["activitywatch", "aw-watcher", "gptme", "ai-agents", "time-tracking"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: Developers",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.10",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
]
|
|
18
|
+
# Zero heavy deps by design: vendored stdlib REST client, no aw-client/aw-core.
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
test = ["pytest>=7.0"]
|
|
23
|
+
dev = ["pytest>=7.0", "mypy>=1.0.0"]
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
aw-watcher-agent = "aw_watcher_agent.cli:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["hatchling"]
|
|
30
|
+
build-backend = "hatchling.build"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/aw_watcher_agent"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 100
|
|
37
|
+
target-version = "py310"
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
check_untyped_defs = true
|
|
41
|
+
warn_return_any = true
|
|
42
|
+
warn_unused_configs = true
|
|
43
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""aw-watcher-agent: log AI coding-assistant sessions to a local aw-server."""
|
|
2
|
+
|
|
3
|
+
from .client import AWClient, AWClientError, Event, DEFAULT_SERVER, utc_now_iso
|
|
4
|
+
|
|
5
|
+
__all__ = ["AWClient", "AWClientError", "Event", "DEFAULT_SERVER", "utc_now_iso"]
|
|
6
|
+
__version__ = "0.2.0"
|