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.
@@ -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"