@agentstrack/collector 0.1.0
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.
- package/CHANGELOG.md +168 -0
- package/LICENSE +202 -0
- package/README.md +779 -0
- package/dist/adapters/account.d.ts +22 -0
- package/dist/adapters/account.js +142 -0
- package/dist/adapters/account.js.map +1 -0
- package/dist/adapters/claude.d.ts +49 -0
- package/dist/adapters/claude.js +259 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.d.ts +64 -0
- package/dist/adapters/codex.js +350 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/opencode.d.ts +79 -0
- package/dist/adapters/opencode.js +338 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/types.d.ts +97 -0
- package/dist/adapters/types.js +20 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +450 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/service.d.ts +3 -0
- package/dist/commands/service.js +86 -0
- package/dist/commands/service.js.map +1 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon.d.ts +57 -0
- package/dist/daemon.js +368 -0
- package/dist/daemon.js.map +1 -0
- package/dist/git/commits.d.ts +34 -0
- package/dist/git/commits.js +85 -0
- package/dist/git/commits.js.map +1 -0
- package/dist/git/repo.d.ts +36 -0
- package/dist/git/repo.js +141 -0
- package/dist/git/repo.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/privacy/mode.d.ts +11 -0
- package/dist/privacy/mode.js +17 -0
- package/dist/privacy/mode.js.map +1 -0
- package/dist/privacy/paths.d.ts +11 -0
- package/dist/privacy/paths.js +30 -0
- package/dist/privacy/paths.js.map +1 -0
- package/dist/privacy/pipeline.d.ts +26 -0
- package/dist/privacy/pipeline.js +112 -0
- package/dist/privacy/pipeline.js.map +1 -0
- package/dist/privacy/redact.d.ts +29 -0
- package/dist/privacy/redact.js +58 -0
- package/dist/privacy/redact.js.map +1 -0
- package/dist/queue/spool.d.ts +47 -0
- package/dist/queue/spool.js +152 -0
- package/dist/queue/spool.js.map +1 -0
- package/dist/queue/tailer.d.ts +25 -0
- package/dist/queue/tailer.js +48 -0
- package/dist/queue/tailer.js.map +1 -0
- package/dist/schema.d.ts +76 -0
- package/dist/schema.js +46 -0
- package/dist/schema.js.map +1 -0
- package/dist/sessions/title.d.ts +2 -0
- package/dist/sessions/title.js +11 -0
- package/dist/sessions/title.js.map +1 -0
- package/dist/transport/client.d.ts +76 -0
- package/dist/transport/client.js +80 -0
- package/dist/transport/client.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
# AgentsTrack Collector
|
|
2
|
+
|
|
3
|
+
**See where your AI coding agents actually spend your time and your tokens — without shipping your code anywhere.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@agentstrack/collector)
|
|
6
|
+
[](https://github.com/agentstrack/collector/actions/workflows/ci.yml)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
`@agentstrack/collector` turns the records Claude Code, Codex and OpenCode already keep on your
|
|
11
|
+
machine into a normalized event stream:
|
|
12
|
+
|
|
13
|
+
- **Tokens and cost** — input, cached input, cache creation, output and reasoning tokens, normalized across every agent. OpenCode's real settled provider cost comes through as `REPORTED`, not an estimate.
|
|
14
|
+
- **Which account paid** — a stable, opaque account key per session, so a personal login and a work one never merge into one bill.
|
|
15
|
+
- **What actually happened** — tool calls, commands, files changed with line counts, and the commits a session produced.
|
|
16
|
+
- **Nothing you didn't agree to** — prompts and code are discarded on your machine, before anything is queued for upload.
|
|
17
|
+
|
|
18
|
+
It **reads what the agent already wrote** — append-only logs for Claude Code and Codex, a read-only
|
|
19
|
+
SQLite query for OpenCode. It does not install hooks, it does not wrap your agent, and it **never
|
|
20
|
+
writes to `~/.claude/settings.json`, `~/.codex/hooks.json` or OpenCode's database**. Uninstalling is `npm rm -g` plus deleting one
|
|
21
|
+
directory; nothing about your agent setup changes.
|
|
22
|
+
|
|
23
|
+
**You do not have to take that on faith.** It is Apache-2.0 and this is the whole of it — the part
|
|
24
|
+
that runs on your machine and reads your files. Prompts and code are dropped locally, before the
|
|
25
|
+
upload queue, and `agentstrack sync --dry-run --print` shows you the literal JSON that would be sent
|
|
26
|
+
before anything is: [Verify it yourself](#verify-it-yourself).
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g @agentstrack/collector # requires Node >= 24
|
|
34
|
+
|
|
35
|
+
agentstrack login at_live_xxxxxxxx_xxxxxxxx # key from Settings → API keys
|
|
36
|
+
agentstrack start # installs a login service and starts collecting
|
|
37
|
+
agentstrack status # confirm it is working
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ agentstrack login at_test_0123456789abcdef_EXAMPLEonly_not_a_real_key_00000…
|
|
42
|
+
✓ Logged in and registered this device.
|
|
43
|
+
Collector: 3f9a1e6c-1c4b-4c7e-9d0f-2a5b8c1d7e40
|
|
44
|
+
Privacy: analytics
|
|
45
|
+
Config: /Users/you/.agentstrack/config.yaml
|
|
46
|
+
|
|
47
|
+
Next: agentstrack start
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```console
|
|
51
|
+
$ agentstrack status
|
|
52
|
+
AgentsTrack collector v0.1.0
|
|
53
|
+
Logged in: yes
|
|
54
|
+
API: https://api.agentstrack.ai
|
|
55
|
+
Collector: 3f9a1e6c-1c4b-4c7e-9d0f-2a5b8c1d7e40
|
|
56
|
+
Privacy: analytics
|
|
57
|
+
Queue depth: 0
|
|
58
|
+
Service: installed
|
|
59
|
+
Running: no
|
|
60
|
+
|
|
61
|
+
Agents
|
|
62
|
+
✓ claude_code 134 transcripts
|
|
63
|
+
✓ codex 13 transcripts
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`Running: no` with `Service: installed` is normal — `Running` tracks a **foreground** collector
|
|
67
|
+
(`agentstrack start --foreground`), which is the only mode that writes a pidfile. The background
|
|
68
|
+
service is supervised by launchd/systemd; `Service: installed` is the line that matters for it.
|
|
69
|
+
|
|
70
|
+
**Your existing history is picked up automatically.** On its first pass the collector reads every
|
|
71
|
+
agent transcript modified in the last 7 days, from byte zero. There is no separate backfill step.
|
|
72
|
+
|
|
73
|
+
Nothing showing up? Run `agentstrack doctor`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## What it collects, and what it never does
|
|
78
|
+
|
|
79
|
+
| ✅ It collects | ❌ It never does |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Session ids, agent name and version, event timestamps | Read your source tree, or open any file other than agent transcripts |
|
|
82
|
+
| Token counts: input, cached input, cache creation, output, reasoning | Upload prompt text — unless you explicitly set `privacy.mode: full` |
|
|
83
|
+
| Model id, provider, stop reason | Upload file contents or diffs — unless you explicitly set `privacy.code_content: full` |
|
|
84
|
+
| Tool names, tool call ids, success/failure | Send tool output or command output |
|
|
85
|
+
| Shell commands, with secrets redacted locally | Send a command that still contains a matched credential |
|
|
86
|
+
| File paths, **relative to the project root by default** | Send absolute paths — which leak your username and your clients' names — unless you opt in |
|
|
87
|
+
| Lines added/removed per edit, computed locally from the tool input | Send the lines themselves |
|
|
88
|
+
| Git branch, commit SHA, additions/deletions/files changed | Send your git remote URL (only a SHA-256 of it) or commit messages and diffs |
|
|
89
|
+
| Locally generated session titles (`analytics` mode and above) | Send the prompt those titles were derived from |
|
|
90
|
+
| Your hostname, OS, arch and each detected agent's version — **once, at registration** | Store the raw hostname server-side (it is kept only as a SHA-256) |
|
|
91
|
+
| | Install hooks or modify `~/.claude/settings.json` / `~/.codex/hooks.json` |
|
|
92
|
+
| | Send `organization_id` or `user_id` — they are not in the wire format at all |
|
|
93
|
+
| | Watch your keyboard, your screen, or any process on your machine |
|
|
94
|
+
|
|
95
|
+
**Two facts worth repeating.**
|
|
96
|
+
|
|
97
|
+
1. **`organization_id` and `user_id` do not exist in the event envelope.** The server derives both
|
|
98
|
+
from your API key. A collector cannot name its own tenant, by construction — and `npm test`
|
|
99
|
+
asserts it — see [`docs/EVENT_SCHEMA.md`](./docs/EVENT_SCHEMA.md).
|
|
100
|
+
2. **Privacy is enforced here, before upload — not on the server.** In `metadata` mode there is no
|
|
101
|
+
content to leak, because it was discarded on your laptop.
|
|
102
|
+
|
|
103
|
+
### Verify it yourself
|
|
104
|
+
|
|
105
|
+
Do not take the table above on trust. **`agentstrack sync --dry-run --print` prints the exact,
|
|
106
|
+
post-redaction JSON that would be uploaded, and sends nothing.** It is the single most useful command
|
|
107
|
+
in the tool: the privacy claims are checkable on your own machine, against your own sessions, before
|
|
108
|
+
a single byte leaves it.
|
|
109
|
+
|
|
110
|
+
```console
|
|
111
|
+
$ agentstrack sync --dry-run
|
|
112
|
+
10 event(s) would be sent to https://api.agentstrack.ai:
|
|
113
|
+
|
|
114
|
+
3 tool.started
|
|
115
|
+
2 model.response
|
|
116
|
+
1 user.prompted
|
|
117
|
+
1 file.read
|
|
118
|
+
1 tool.completed
|
|
119
|
+
1 file.changed
|
|
120
|
+
1 command.executed
|
|
121
|
+
|
|
122
|
+
Re-run with --print to see the full event bodies.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```console
|
|
126
|
+
$ agentstrack sync --dry-run --print
|
|
127
|
+
10 event(s) would be sent to https://api.agentstrack.ai:
|
|
128
|
+
|
|
129
|
+
{
|
|
130
|
+
"occurred_at": "2026-08-26T10:00:00.000Z",
|
|
131
|
+
"session_id": "06f3470f-d924-4552-b3ee-3f8924286cec",
|
|
132
|
+
"agent": "claude_code",
|
|
133
|
+
"agent_version": "2.1.241",
|
|
134
|
+
"event_type": "user.prompted",
|
|
135
|
+
"payload": {
|
|
136
|
+
"prompt_chars": 81,
|
|
137
|
+
"derived_title": "Add tests for the cost calculator",
|
|
138
|
+
"repo": {
|
|
139
|
+
"branch": "feature/pricing"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"event_id": "fbaa5b45-7172-4b52-93c8-565aa281d51d",
|
|
143
|
+
"schema_version": 1,
|
|
144
|
+
"collector_id": "3f9a1e6c-1c4b-4c7e-9d0f-2a5b8c1d7e40"
|
|
145
|
+
}
|
|
146
|
+
…
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
That is a real `user.prompted` event in the default `analytics` mode. Read what is **not** there:
|
|
150
|
+
no `prompt_text` — the 81-character prompt it was derived from was discarded on the machine — no
|
|
151
|
+
`organization_id`, no `user_id`, no `project_path`. Set `privacy.prompts: never` and re-run, and
|
|
152
|
+
`derived_title` disappears too.
|
|
153
|
+
|
|
154
|
+
Two companion commands:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
agentstrack config --show-effective # the policy actually in force, defaults included
|
|
158
|
+
agentstrack doctor --json # structured diagnostics, safe to paste into an issue
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## How it works
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
~/.claude/projects/<slug>/<session-uuid>.jsonl
|
|
167
|
+
~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl
|
|
168
|
+
│ ~/.local/share/opencode/opencode.db
|
|
169
|
+
│ append-only files │
|
|
170
|
+
▼ ▼
|
|
171
|
+
┌────────────────────────┐ ┌────────────────────────┐
|
|
172
|
+
│ tailer │ │ db poller │ same 5s cycle
|
|
173
|
+
│ (read-only, resumable) │ │ (READ-ONLY, no writes) │ time_updated cursors
|
|
174
|
+
│ (path,inode,offset) cp │ │ in spool.db meta │ in spool.db meta
|
|
175
|
+
└───────────┬────────────┘ └───────────┬────────────┘
|
|
176
|
+
└───────────────┬───────────────────┘
|
|
177
|
+
▼
|
|
178
|
+
┌────────────────────────┐
|
|
179
|
+
│ privacy pipeline │ mode-based content strip
|
|
180
|
+
│ │ → 15 built-in secret rules + org rules
|
|
181
|
+
│ │ → path normalization
|
|
182
|
+
│ │ → raw content DISCARDED HERE
|
|
183
|
+
└───────────┬────────────┘
|
|
184
|
+
▼
|
|
185
|
+
┌────────────────────────┐
|
|
186
|
+
│ ~/.agentstrack/ │ SQLite (WAL) — durable across crash,
|
|
187
|
+
│ spool.db │ restart and reboot
|
|
188
|
+
└───────────┬────────────┘
|
|
189
|
+
▼
|
|
190
|
+
┌────────────────────────┐
|
|
191
|
+
│ batched HTTPS + gzip │ POST /v1/events/batch, 100 events / 30s
|
|
192
|
+
│ exponential backoff │ idempotent on event_id
|
|
193
|
+
└────────────────────────┘
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Offline is a normal state, not an error.** With no network, a blocked VPN or a dead API, the
|
|
197
|
+
collector keeps parsing and keeps spooling; when the network returns it drains oldest-first. Bodies
|
|
198
|
+
over 1 KB are gzipped.
|
|
199
|
+
|
|
200
|
+
**Restart is safe.** File read offsets live in the same SQLite database as the queue, keyed by
|
|
201
|
+
`(path, inode)`. A restart resumes mid-file. If a file is replaced (new inode) or truncated (offset
|
|
202
|
+
past the end), it is re-read from the start rather than silently skipped.
|
|
203
|
+
|
|
204
|
+
**A line the agent is still writing is never consumed.** The tailer advances its checkpoint only as
|
|
205
|
+
far as the **last complete newline**; a partial trailing line is left unread and picked up whole on
|
|
206
|
+
the next pass, once the agent has terminated it. This matters because the collector reads live
|
|
207
|
+
sessions: without it, every mid-write read would emit half a JSON object *and* orphan its remainder,
|
|
208
|
+
so both halves would fail to parse and that event would be lost. Byte offsets are computed from the
|
|
209
|
+
raw buffer, not from decoded text, so a multi-byte character cannot desynchronise the position
|
|
210
|
+
either.
|
|
211
|
+
|
|
212
|
+
**Backpressure is handled.** A `413` halves the batch size and the collector recovers it on the next
|
|
213
|
+
success. A `5xx`, a timeout, a `408` or a `429` is retried with jittered exponential backoff (1s
|
|
214
|
+
base, capped at 5 minutes). A `4xx` that is none of those means the server will never accept the
|
|
215
|
+
batch: **the attempt counter is incremented for the events in that batch only, and one of them is
|
|
216
|
+
deleted once it reaches `upload.max_retries` (default 8)**. It is not parked and it does not come
|
|
217
|
+
back — a poison event must not be able to block the queue forever.
|
|
218
|
+
|
|
219
|
+
Two properties of that deletion are worth stating explicitly, because both are easy to get wrong:
|
|
220
|
+
|
|
221
|
+
- **It is scoped to the failing batch.** An event sitting elsewhere in the spool cannot be destroyed
|
|
222
|
+
by a batch it was not part of.
|
|
223
|
+
- **At least one attempt is always allowed.** `max_retries: 0` is clamped to `1`; a value of zero
|
|
224
|
+
would otherwise match every never-attempted row and empty the whole queue on the first failure.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Privacy
|
|
229
|
+
|
|
230
|
+
### The three modes
|
|
231
|
+
|
|
232
|
+
| | `metadata` | `analytics` *(default)* | `full` |
|
|
233
|
+
|---|---|---|---|
|
|
234
|
+
| Token counts, timings, models, costs | ✅ | ✅ | ✅ |
|
|
235
|
+
| Tool names and outcomes | ✅ | ✅ | ✅ |
|
|
236
|
+
| Commands (secret-redacted) | ✅ | ✅ | ✅ |
|
|
237
|
+
| File paths, line counts | ✅ | ✅ | ✅ |
|
|
238
|
+
| Git branch / SHA / remote **hash** | ✅ | ✅ | ✅ |
|
|
239
|
+
| Locally derived session titles | ❌ stripped | ✅ | ✅ |
|
|
240
|
+
| Error messages | ❌ stripped | ✅ | ✅ |
|
|
241
|
+
| Prompt text | ❌ never | ❌ never | ⚠️ only with `privacy.prompts: full` |
|
|
242
|
+
| File contents / diffs | ❌ never | ❌ never | ⚠️ only with `privacy.code_content: full` |
|
|
243
|
+
|
|
244
|
+
`analytics` is the honest middle: the title is computed **on your machine** from the prompt, and then
|
|
245
|
+
the prompt is deleted. The server receives `"fix flaky auth test"`, never the 900 words you typed.
|
|
246
|
+
|
|
247
|
+
If even the title is too much, `privacy.prompts: never` drops that too: in `analytics` it strips
|
|
248
|
+
`derived_title`, so nothing derived from a prompt leaves the machine, without giving up token, tool
|
|
249
|
+
and cost analytics the way `metadata` does. `metadata` already drops the title by mode.
|
|
250
|
+
|
|
251
|
+
`never` means never, in every mode — including `full`, where it strips both `prompt_text` and
|
|
252
|
+
`derived_title`. Setting it is the strongest prompt-privacy guarantee available without dropping to
|
|
253
|
+
`metadata`.
|
|
254
|
+
|
|
255
|
+
`full` is opt-in twice over: setting `mode: full` alone changes nothing about prompts or code —
|
|
256
|
+
you must also set `privacy.prompts: full` and `privacy.code_content: full`. Nothing in the product
|
|
257
|
+
nags you to.
|
|
258
|
+
|
|
259
|
+
### Org policy is a ceiling, never a floor
|
|
260
|
+
|
|
261
|
+
Your organization's default mode arrives in the `POST /v1/collector/register` response at login, and
|
|
262
|
+
is re-read from `GET /v1/collector/config` on every daemon start. **A local setting that is stricter
|
|
263
|
+
always wins.** An org set to `full` cannot widen a laptop configured for `metadata`; an org set to
|
|
264
|
+
`metadata` does clamp a laptop asking for `full`. The clamp is one shared function so login and the
|
|
265
|
+
daemon cannot drift.
|
|
266
|
+
|
|
267
|
+
### Built-in secret redaction
|
|
268
|
+
|
|
269
|
+
Every free-text field that survives the mode strip (`prompt_text`, `derived_title`, `message`,
|
|
270
|
+
`command`) passes through these 15 rules, most-specific first, on your machine:
|
|
271
|
+
|
|
272
|
+
| Rule | Catches |
|
|
273
|
+
|---|---|
|
|
274
|
+
| `anthropic_key` | `sk-ant-…` |
|
|
275
|
+
| `openai_key` | `sk-…`, `sk-proj-…` |
|
|
276
|
+
| `github_token` | `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` |
|
|
277
|
+
| `github_pat` | `github_pat_…` |
|
|
278
|
+
| `slack_token` | `xoxb-`, `xoxa-`, `xoxp-`, `xoxr-`, `xoxs-` |
|
|
279
|
+
| `stripe_key` | `sk_live_`, `sk_test_`, `rk_live_`, `rk_test_` |
|
|
280
|
+
| `aws_access_key` | `AKIA…`, `ASIA…` |
|
|
281
|
+
| `google_api_key` | `AIza…` |
|
|
282
|
+
| `agentstrack_key` | our own `at_live_…` / `at_test_…` keys |
|
|
283
|
+
| `private_key` | any `-----BEGIN … PRIVATE KEY-----` block |
|
|
284
|
+
| `jwt` | three-segment `eyJ…` tokens |
|
|
285
|
+
| `bearer_header` | `Bearer <token>` → `Bearer [REDACTED]` |
|
|
286
|
+
| `basic_auth_url` | `https://user:pw@host` → `https://[REDACTED]@host` |
|
|
287
|
+
| `env_assignment` | `*SECRET*=`, `*TOKEN*=`, `*PASSWORD*=`, `*PASSWD*=`, `*APIKEY*=`, `*API_KEY*=`, `*ACCESS_KEY*=`, `*PRIVATE_KEY*=` |
|
|
288
|
+
| `generic_hex_secret` | bare hex strings of 40+ characters |
|
|
289
|
+
|
|
290
|
+
A match is replaced in place, and most rules substitute `[REDACTED:rule_name]`. Four do not:
|
|
291
|
+
`bearer_header` → `Bearer [REDACTED]` and `basic_auth_url` → `scheme://[REDACTED]@host` keep the
|
|
292
|
+
surrounding syntax so the shape of the command survives, `env_assignment` → `NAME=[REDACTED]` keeps
|
|
293
|
+
the variable name, and `generic_hex_secret` substitutes the shorter `[REDACTED:hex]`. Your
|
|
294
|
+
organization can add patterns server-side; a malformed org pattern is skipped rather than breaking
|
|
295
|
+
the collector.
|
|
296
|
+
|
|
297
|
+
Redaction is defence in depth, not the primary control. The primary control is that in `metadata`
|
|
298
|
+
and `analytics` modes the content is **deleted locally** and never enters the pipeline at all.
|
|
299
|
+
|
|
300
|
+
### How file paths are handled
|
|
301
|
+
|
|
302
|
+
With `file_paths: relative` (the default):
|
|
303
|
+
|
|
304
|
+
| Actual path | Uploaded as |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `/Users/dana/work/api/src/auth.ts` (project root `/Users/dana/work/api`) | `src/auth.ts` |
|
|
307
|
+
| `/Users/dana/.zshrc` | `~/.zshrc` |
|
|
308
|
+
| `/etc/nginx/sites-enabled/default` | `…/sites-enabled/default` |
|
|
309
|
+
|
|
310
|
+
The project root itself (`repo.project_path`) is **dropped entirely** in `never` and `relative`
|
|
311
|
+
modes — it is only transmitted if you opt into `file_paths: absolute`. Repositories are correlated
|
|
312
|
+
by `remote_hash`, a SHA-256 of the normalized remote URL, not by path.
|
|
313
|
+
|
|
314
|
+
### Excluding a project entirely
|
|
315
|
+
|
|
316
|
+
```yaml
|
|
317
|
+
privacy:
|
|
318
|
+
excluded_projects:
|
|
319
|
+
- "~/work/client-under-nda"
|
|
320
|
+
- "/Users/me/personal"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Prefix match on the session's working directory, `~` expands. An excluded project produces **no
|
|
324
|
+
events at all** — not even counts. Edit the YAML and restart the collector.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Commands
|
|
329
|
+
|
|
330
|
+
| Command | What it does |
|
|
331
|
+
|---|---|
|
|
332
|
+
| `agentstrack login <api-key>` | Register this device, store the key, apply the org privacy ceiling |
|
|
333
|
+
| `agentstrack logout [--purge]` | Remove the stored key; `--purge` also deletes the unsent spool |
|
|
334
|
+
| `agentstrack start [-f]` | Install + start the login service, or run in this terminal with `-f` |
|
|
335
|
+
| `agentstrack stop` | Stop the collector and remove its service unit |
|
|
336
|
+
| `agentstrack status [--json]` | Health, queue depth, detected agents |
|
|
337
|
+
| `agentstrack doctor [--json]` | Diagnose setup problems; `--json` is what bug reports want |
|
|
338
|
+
| `agentstrack config [--path\|--show-effective]` | Print the config (API key masked) |
|
|
339
|
+
| `agentstrack sync [--dry-run [--print]]` | Upload queued events now, or show what would be sent |
|
|
340
|
+
| `agentstrack service <install\|uninstall>` | Manage the login service without starting a collector |
|
|
341
|
+
|
|
342
|
+
### `login`
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
agentstrack login <api-key> [--api-url <url>] [--label <name>]
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
The key is a **positional argument** — there is no interactive prompt and no environment variable.
|
|
349
|
+
`--api-url` points at a self-hosted instance; `--label` names this machine in the dashboard.
|
|
350
|
+
Registration is idempotent on (user, hostname hash), so re-running `login` on the same machine reuses
|
|
351
|
+
the existing collector instead of fragmenting its history. The config file is written mode `600`, in
|
|
352
|
+
a directory created mode `700`.
|
|
353
|
+
|
|
354
|
+
The registration payload is `hostname`, `label`, `os`, `arch`, the collector's own version, and one
|
|
355
|
+
entry per configured agent: `{ agent, version }`. The **agent version is the real one**, read out of
|
|
356
|
+
a transcript the agent already wrote (`2.1.247`, say, from Claude Code's `version` field). Where an
|
|
357
|
+
adapter cannot cheaply establish a version at detection time the field is simply **omitted** rather
|
|
358
|
+
than filled with a placeholder, so a missing version in the dashboard means "not reported", never
|
|
359
|
+
"not detected".
|
|
360
|
+
|
|
361
|
+
Both shipping adapters report a real version. Claude Code reads it from a transcript
|
|
362
|
+
(e.g. `2.1.247`); Codex reads `session_meta.cli_version` from its newest rollout
|
|
363
|
+
(e.g. `0.149.0-alpha.4.3`). The same value is attached to every event as `agent_version`.
|
|
364
|
+
|
|
365
|
+
If your local privacy mode is stricter than the org's, login says so and keeps yours:
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
Privacy: metadata (your local setting; org allows analytics)
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### `start` / `stop`
|
|
372
|
+
|
|
373
|
+
`agentstrack start` writes a **launchd** agent on macOS (`~/Library/LaunchAgents/ai.agentstrack.collector.plist`)
|
|
374
|
+
or a **systemd user unit** on Linux (`~/.config/systemd/user/agentstrack.service`), loads it, and
|
|
375
|
+
returns. Neither needs root. `-f` / `--foreground` runs in the terminal instead — best for a first
|
|
376
|
+
run, and the only mode where `status` reports `Running: yes`.
|
|
377
|
+
|
|
378
|
+
`agentstrack stop` removes the service unit *and* signals a foreground collector. There is no
|
|
379
|
+
"stop but keep the unit"; use `agentstrack service install` to put it back.
|
|
380
|
+
|
|
381
|
+
### `doctor`
|
|
382
|
+
|
|
383
|
+
```console
|
|
384
|
+
$ agentstrack doctor
|
|
385
|
+
Configuration
|
|
386
|
+
✓ config exists at /Users/you/.agentstrack/config.yaml
|
|
387
|
+
✓ API key present
|
|
388
|
+
✓ device registered
|
|
389
|
+
|
|
390
|
+
Agents
|
|
391
|
+
✓ claude_code transcripts found
|
|
392
|
+
225 file(s) modified in the last 7 days
|
|
393
|
+
✓ codex transcripts found
|
|
394
|
+
3 file(s) modified in the last 7 days
|
|
395
|
+
|
|
396
|
+
Connectivity
|
|
397
|
+
✗ API reachable at https://api.agentstrack.ai
|
|
398
|
+
fetch failed
|
|
399
|
+
|
|
400
|
+
Queue
|
|
401
|
+
✓ queue depth 0
|
|
402
|
+
log: /Users/you/.agentstrack/collector.log
|
|
403
|
+
|
|
404
|
+
1 problem(s) found.
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Exit code is non-zero when there is a problem, so it works in a monitoring check.
|
|
408
|
+
`agentstrack doctor --json` prints the structured form, which is what the bug template asks for:
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"version": "0.1.0",
|
|
413
|
+
"node": "v22.22.0",
|
|
414
|
+
"platform": "darwin-arm64",
|
|
415
|
+
"configured": true,
|
|
416
|
+
"logged_in": true,
|
|
417
|
+
"collector_id": "3f9a1e6c-1c4b-4c7e-9d0f-2a5b8c1d7e40",
|
|
418
|
+
"privacy_mode": "analytics",
|
|
419
|
+
"api_url": "https://api.agentstrack.ai",
|
|
420
|
+
"api": { "reachable": true, "privacy_mode": "analytics" },
|
|
421
|
+
"queue_depth": 0,
|
|
422
|
+
"running": false,
|
|
423
|
+
"service_installed": true,
|
|
424
|
+
"agents": [
|
|
425
|
+
{ "agent": "claude_code", "installed": true, "healthy": true, "files_tracked": 134, "note": null },
|
|
426
|
+
{ "agent": "codex", "installed": true, "healthy": true, "files_tracked": 13, "note": null }
|
|
427
|
+
],
|
|
428
|
+
"log_path": "/Users/you/.agentstrack/collector.log"
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
It contains no API key, no prompt, no path inside a project — it is safe to paste into an issue.
|
|
433
|
+
|
|
434
|
+
### `config`
|
|
435
|
+
|
|
436
|
+
`agentstrack config` prints the file as it is on disk with the key masked. `--path` prints the path
|
|
437
|
+
only. `--show-effective` prints the config **after every default is applied** — the policy the
|
|
438
|
+
collector actually runs with:
|
|
439
|
+
|
|
440
|
+
```console
|
|
441
|
+
$ agentstrack config --show-effective
|
|
442
|
+
api_url: https://api.agentstrack.ai
|
|
443
|
+
privacy:
|
|
444
|
+
mode: analytics
|
|
445
|
+
prompts: local_summary_only
|
|
446
|
+
code_content: never
|
|
447
|
+
file_paths: relative
|
|
448
|
+
shell_arguments: redact_secrets
|
|
449
|
+
excluded_projects: []
|
|
450
|
+
tracking:
|
|
451
|
+
idle_timeout_seconds: 120
|
|
452
|
+
git_metadata: true
|
|
453
|
+
process_metrics: true
|
|
454
|
+
agents:
|
|
455
|
+
- claude_code
|
|
456
|
+
- codex
|
|
457
|
+
- opencode
|
|
458
|
+
upload:
|
|
459
|
+
batch_size: 100
|
|
460
|
+
interval_seconds: 30
|
|
461
|
+
max_retries: 8
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
There is no `config set` — edit the YAML. An invalid config is a **hard error**, never a silent
|
|
465
|
+
fallback to defaults, so a typo cannot quietly widen your privacy mode.
|
|
466
|
+
|
|
467
|
+
### `sync`
|
|
468
|
+
|
|
469
|
+
Drains the spool once and exits — useful after a network outage, or from a cron job on a machine
|
|
470
|
+
where you would rather not run a daemon. It does **not** take a time window; the daemon's own scan is
|
|
471
|
+
what reads new transcript lines.
|
|
472
|
+
|
|
473
|
+
```console
|
|
474
|
+
$ agentstrack sync
|
|
475
|
+
✓ Queue is already empty.
|
|
476
|
+
|
|
477
|
+
$ agentstrack sync --dry-run
|
|
478
|
+
✓ Nothing queued — nothing would be sent.
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
With a backlog it prints `Uploading <n> queued events…` and then either `✓ Uploaded <n> events.` or,
|
|
482
|
+
if some remain, a warning naming the log — and exits non-zero, so it is safe to run from cron.
|
|
483
|
+
|
|
484
|
+
`--dry-run` sends nothing at all and prints a breakdown by event type; `--print` adds the full
|
|
485
|
+
post-redaction JSON body of each one — see [Verify it yourself](#verify-it-yourself). Both inspect
|
|
486
|
+
the head of the queue, up to `upload.batch_size` events, so `100 event(s) would be sent` on a large
|
|
487
|
+
backlog means "the next batch", not "the whole spool" — `agentstrack status` reports the true depth.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Configuration
|
|
492
|
+
|
|
493
|
+
`~/.agentstrack/config.yaml`, mode `600` because it holds an API key. Set `AGENTSTRACK_HOME` to
|
|
494
|
+
relocate the whole directory (config, spool, log, pidfile). Every key has a default — an empty file
|
|
495
|
+
is a valid config. This is the complete set:
|
|
496
|
+
|
|
497
|
+
```yaml
|
|
498
|
+
# --- Connection ---------------------------------------------------------
|
|
499
|
+
api_url: https://api.agentstrack.ai # change for a self-hosted instance
|
|
500
|
+
api_key: at_live_xxxxxxxxxxxxxxxx_xxxx # written by `agentstrack login`. Never commit.
|
|
501
|
+
collector_id: 3f9a1e6c-… # assigned by the server at registration
|
|
502
|
+
|
|
503
|
+
# --- Privacy ------------------------------------------------------------
|
|
504
|
+
privacy:
|
|
505
|
+
# metadata | analytics (default) | full — see the table above
|
|
506
|
+
mode: analytics
|
|
507
|
+
|
|
508
|
+
# never | local_summary_only (default) | full
|
|
509
|
+
# `full` is what keeps `mode: full` from uploading prompt text unless you also
|
|
510
|
+
# ask for it here. `never` suppresses prompt text in every mode, and in
|
|
511
|
+
# `analytics` it additionally drops the locally derived `derived_title`, so
|
|
512
|
+
# nothing derived from a prompt leaves the machine at all. Under `mode: full`
|
|
513
|
+
# it does NOT drop `derived_title` — see the privacy section.
|
|
514
|
+
prompts: local_summary_only
|
|
515
|
+
|
|
516
|
+
# never (default) | full
|
|
517
|
+
# `never` drops file contents and diffs in EVERY mode, so opting into `full`
|
|
518
|
+
# prompts does not silently opt into shipping source code.
|
|
519
|
+
code_content: never
|
|
520
|
+
|
|
521
|
+
# never | relative (default) | absolute
|
|
522
|
+
# relative: paths relative to the project root; `~` for home; last two
|
|
523
|
+
# segments for anything else. The project root is only sent under `absolute`.
|
|
524
|
+
file_paths: relative
|
|
525
|
+
|
|
526
|
+
# never | redact_secrets (default) | full
|
|
527
|
+
# `never` truncates a command to its first whitespace token. The other two
|
|
528
|
+
# keep the command line; secret redaction is applied either way.
|
|
529
|
+
shell_arguments: redact_secrets
|
|
530
|
+
|
|
531
|
+
# Prefix match on the session's working directory. `~` expands.
|
|
532
|
+
# An excluded project produces no events of any kind.
|
|
533
|
+
excluded_projects: []
|
|
534
|
+
|
|
535
|
+
# --- Tracking -----------------------------------------------------------
|
|
536
|
+
tracking:
|
|
537
|
+
# Read by the server, not by the collector — see "Roadmap". 30–3600.
|
|
538
|
+
idle_timeout_seconds: 120
|
|
539
|
+
|
|
540
|
+
# Read git branch, project root and a HASH of the remote; poll `git log` for
|
|
541
|
+
# commits made during a session. `false` means no git process is ever spawned.
|
|
542
|
+
git_metadata: true
|
|
543
|
+
|
|
544
|
+
# Not implemented yet — see "Roadmap".
|
|
545
|
+
process_metrics: true
|
|
546
|
+
|
|
547
|
+
# Which adapters to run. Removing one stops it being read entirely.
|
|
548
|
+
agents:
|
|
549
|
+
- claude_code
|
|
550
|
+
- codex
|
|
551
|
+
|
|
552
|
+
# --- Upload -------------------------------------------------------------
|
|
553
|
+
upload:
|
|
554
|
+
batch_size: 100 # events per request. 1–500 (server caps at 500).
|
|
555
|
+
interval_seconds: 30 # seconds between flushes. 5–600.
|
|
556
|
+
max_retries: 8 # non-retryable failures before an event is DELETED. 0–20;
|
|
557
|
+
# 0 is clamped to 1, since "zero attempts allowed" would
|
|
558
|
+
# match every queued event on the first failure.
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Environment overrides: `AGENTSTRACK_HOME` (all local state), `CLAUDE_CONFIG_DIR` (default
|
|
562
|
+
`~/.claude`), `CODEX_HOME` (default `~/.codex`), `OPENCODE_DATA_DIR` (default
|
|
563
|
+
`$XDG_DATA_HOME/opencode`, falling back to `~/.local/share/opencode`) and `OPENCODE_DB` (the
|
|
564
|
+
database filename or an absolute path — the same override OpenCode itself honours).
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Supported agents
|
|
569
|
+
|
|
570
|
+
| Agent | Status | Reads |
|
|
571
|
+
|---|---|---|
|
|
572
|
+
| **Claude Code** | ✅ Stable | `~/.claude/projects/<slug>/<session-uuid>.jsonl` |
|
|
573
|
+
| **Codex** | ✅ Stable | `~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl` |
|
|
574
|
+
| **OpenCode** | ✅ Stable | `~/.local/share/opencode/opencode.db` — SQLite, opened **read-only** |
|
|
575
|
+
| Gemini CLI · Cursor · Cline · Copilot CLI | 🗓 Planned | ids reserved in the schema, no adapter yet |
|
|
576
|
+
|
|
577
|
+
Per-signal honesty — the adapters do not produce identical data, because the agents do not record
|
|
578
|
+
identical things:
|
|
579
|
+
|
|
580
|
+
| Signal | Claude Code | Codex | OpenCode |
|
|
581
|
+
|---|---|---|---|
|
|
582
|
+
| Session start | ➖ inferred from the first event | ✅ from `session_meta` | ✅ from the `session` row |
|
|
583
|
+
| Session end | ➖ inferred | ➖ inferred | ✅ on archive or compaction |
|
|
584
|
+
| Prompts / titles | ✅ | ✅ | ✅ (OpenCode names its own sessions) |
|
|
585
|
+
| Per-response token usage | ✅ `model.response` with full cache breakdown | ➖ cumulative snapshots only | ➖ cumulative per-session totals only |
|
|
586
|
+
| **Provider-reported cost** | ➖ estimated from a rate card | ➖ estimated | ✅ **`REPORTED` — the real settled charge** |
|
|
587
|
+
| Turn boundaries | ➖ not logged | ✅ `agent.turn.started` / `.ended` | ➖ not emitted |
|
|
588
|
+
| Tool calls | ✅ | ✅ (incl. `custom_tool_call`) | ✅ terminal event with a real duration |
|
|
589
|
+
| Commands | ✅ from `Bash` tool input | ✅ with exit code and duration | ✅ from the `bash` tool's input |
|
|
590
|
+
| File changes + line counts | ✅ from `Edit`/`Write`/`NotebookEdit` inputs | ✅ parsed from the `apply_patch` body | ✅ from `edit`/`write` tool inputs |
|
|
591
|
+
| File reads | ✅ from the `Read` tool | ➖ heuristic, from `cat`/`head`/`tail`/`sed`/`nl`/`bat`/`less` | ✅ from the `read` tool |
|
|
592
|
+
| Agent errors | ➖ not logged | ✅ `error` | ➖ not emitted |
|
|
593
|
+
| Commits | ✅ (from `git log`, not the transcript) | ✅ (same) | ✅ (same) |
|
|
594
|
+
| Plan / subscription type | ➖ | ✅ `plan_type` | ➖ |
|
|
595
|
+
| Account attribution | ✅ from `~/.claude.json` | ➖ no account file | ✅ from `account.json`, per provider |
|
|
596
|
+
|
|
597
|
+
Every adapter is read-only. Adapter formats drift between agent releases: an unparseable line is
|
|
598
|
+
skipped, never fatal to the file.
|
|
599
|
+
|
|
600
|
+
### OpenCode is a live database, not a log
|
|
601
|
+
|
|
602
|
+
OpenCode keeps sessions, messages and message parts in SQLite — the same file its UI is writing to
|
|
603
|
+
while you work. So this adapter does not tail; it polls, on the same 5-second cycle as the tailer,
|
|
604
|
+
and it takes deliberate care not to be the reason your editor stutters or your history breaks:
|
|
605
|
+
|
|
606
|
+
- opened `readonly` **and** `fileMustExist`, with `PRAGMA query_only` — a bug here cannot write,
|
|
607
|
+
migrate or create anything;
|
|
608
|
+
- `PRAGMA busy_timeout` so a concurrent OpenCode write makes us wait briefly instead of failing;
|
|
609
|
+
- one short query at a time, then the handle is closed. No long transactions, ever.
|
|
610
|
+
|
|
611
|
+
Because `(path, inode, offset)` means nothing to a database, resumption uses three `time_updated`
|
|
612
|
+
cursors in the collector's own spool. A first-ever run reaches back 7 days, the same horizon the
|
|
613
|
+
tailer uses for transcripts.
|
|
614
|
+
|
|
615
|
+
### Which account did this?
|
|
616
|
+
|
|
617
|
+
One machine often drives several accounts. Each session carries a stable, opaque `account.key`
|
|
618
|
+
(Claude Code's `accountUuid`; OpenCode's `<serviceID>:<accountId>`) so their costs never merge. The
|
|
619
|
+
readable half — email, organization name — is PII and is stripped in `metadata` mode, where sessions
|
|
620
|
+
still split correctly but the account shows as opaque.
|
|
621
|
+
|
|
622
|
+
**The credential is never read.** OpenCode's `account.json` stores a live API key next to the account
|
|
623
|
+
id; only `id` and `serviceID` are touched, and `auth.json` is never opened at all.
|
|
624
|
+
|
|
625
|
+
**Live events only.** These files record who is signed in *now* and are rewritten on account switch,
|
|
626
|
+
so events that predate the collector's start carry **no** account rather than today's — a
|
|
627
|
+
retroactive guess would look exactly like a fact.
|
|
628
|
+
|
|
629
|
+
Want an agent that is not here? Open an
|
|
630
|
+
[agent support request](https://github.com/agentstrack/collector/issues/new?template=agent_support.yml),
|
|
631
|
+
or write it — see [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Self-hosting
|
|
636
|
+
|
|
637
|
+
The collector speaks plain REST over HTTPS. Point it anywhere:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
agentstrack login <api-key> --api-url https://agentstrack.internal.example.com
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Or set `api_url` in `config.yaml` and restart. These are the only endpoints it calls:
|
|
644
|
+
|
|
645
|
+
| Method | Path | When |
|
|
646
|
+
|---|---|---|
|
|
647
|
+
| `POST` | `/v1/collector/register` | `login`, and once on daemon start if `collector_id` is missing |
|
|
648
|
+
| `GET` | `/v1/collector/config` | `doctor`, and each daemon start — org privacy ceiling + redaction rules. Not called by `login`: the register response already carries the ceiling |
|
|
649
|
+
| `POST` | `/v1/collector/health` | Every 60s while running — queue depth, version, detected agents |
|
|
650
|
+
| `POST` | `/v1/events/batch` | Every `upload.interval_seconds`, or when the queue reaches `batch_size` |
|
|
651
|
+
|
|
652
|
+
Authentication is `Authorization: Bearer <api_key>` on every request. Batch bodies over 1 KB are
|
|
653
|
+
gzipped (`content-encoding: gzip`).
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Troubleshooting
|
|
658
|
+
|
|
659
|
+
**Start here: `agentstrack doctor`.** It checks every failure mode below.
|
|
660
|
+
|
|
661
|
+
### No sessions appearing
|
|
662
|
+
|
|
663
|
+
1. `agentstrack status` — is `Service: installed` (or `Running: yes` for a foreground run), and does
|
|
664
|
+
each agent show a transcript count above zero?
|
|
665
|
+
2. Do the transcripts exist and are they recent? The collector only reads files modified in the last
|
|
666
|
+
**7 days**:
|
|
667
|
+
```bash
|
|
668
|
+
ls -lt ~/.claude/projects/*/*.jsonl | head
|
|
669
|
+
find ~/.codex/sessions -name '*.jsonl' -mtime -7 | head
|
|
670
|
+
```
|
|
671
|
+
3. Is the project on your exclusion list? `agentstrack config --show-effective | grep -A3 excluded`
|
|
672
|
+
4. Is the agent enabled under `tracking.agents`?
|
|
673
|
+
5. Is anything queued but stuck? `agentstrack sync --dry-run` shows the head of the queue.
|
|
674
|
+
|
|
675
|
+
### The queue is not draining
|
|
676
|
+
|
|
677
|
+
`agentstrack status` shows `Queue depth` climbing. Check `agentstrack doctor`, then the log:
|
|
678
|
+
|
|
679
|
+
| Symptom | Cause | Fix |
|
|
680
|
+
|---|---|---|
|
|
681
|
+
| `401` in the log | Key revoked or wrong | `agentstrack login <new-key>` |
|
|
682
|
+
| `403` | Key lacks ingest permission | Issue a new key |
|
|
683
|
+
| `fetch failed`, `ETIMEDOUT` | Network, VPN or proxy | Set `HTTPS_PROXY`; events keep spooling meanwhile |
|
|
684
|
+
| `Server rejected the batch as too large` | Batch above the server's limit | Automatic — batch size halves and recovers |
|
|
685
|
+
| `Batch permanently rejected: … (dropped N)` | Non-retryable `4xx` | N events **in that batch** hit `max_retries` and were deleted. Nothing outside the batch is touched. Check the API version matches the collector's schema. |
|
|
686
|
+
|
|
687
|
+
Retryable failures never lose anything: `spool.db` is durable across restarts and reboots, and
|
|
688
|
+
draining resumes automatically.
|
|
689
|
+
|
|
690
|
+
### Permission errors
|
|
691
|
+
|
|
692
|
+
```
|
|
693
|
+
EACCES: permission denied, open '/Users/you/.claude/projects/…/abc.jsonl'
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
The collector runs as **you** and needs read access to the agent log directories plus read/write on
|
|
697
|
+
`~/.agentstrack`. It never needs root — do not run it with `sudo`, since a root-owned spool is the
|
|
698
|
+
usual cause of this error showing up later.
|
|
699
|
+
|
|
700
|
+
```bash
|
|
701
|
+
ls -ld ~/.agentstrack ~/.claude/projects ~/.codex/sessions
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
`~/.agentstrack` is created mode `700`, `config.yaml` and `spool.db` (with its `-wal` / `-shm`
|
|
705
|
+
files) mode `600` — the collector sets those itself, so you should not have to. If an older install
|
|
706
|
+
or a `sudo` run left them wider, this puts them back:
|
|
707
|
+
|
|
708
|
+
```bash
|
|
709
|
+
chmod 700 ~/.agentstrack && chmod 600 ~/.agentstrack/config.yaml ~/.agentstrack/spool.db*
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
On macOS, if your agent directories sit under Documents or Desktop, grant your terminal (and, for the
|
|
713
|
+
service, `node`) Full Disk Access in System Settings → Privacy & Security.
|
|
714
|
+
|
|
715
|
+
### Reading the log
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
tail -f ~/.agentstrack/collector.log
|
|
719
|
+
grep -i "error\|failed\|rejected" ~/.agentstrack/collector.log | tail -20
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
The log records counts, queue depths and `event_id`s — never payloads, prompts, code or keys. That is
|
|
723
|
+
what makes it safe to attach to an issue. Please attach `agentstrack doctor --json` too.
|
|
724
|
+
|
|
725
|
+
### Complete reset
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
agentstrack stop
|
|
729
|
+
rm -rf ~/.agentstrack # config, spool, log, pidfile — all local state
|
|
730
|
+
agentstrack login <api-key>
|
|
731
|
+
agentstrack start
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
---
|
|
735
|
+
|
|
736
|
+
## Roadmap
|
|
737
|
+
|
|
738
|
+
Honest list of things that are **not** in 0.1.0, so you do not go looking for them.
|
|
739
|
+
[ROADMAP.md](./ROADMAP.md) has the same list with the design constraints and what "help wanted"
|
|
740
|
+
means for each.
|
|
741
|
+
|
|
742
|
+
- **Backfill window control** (`sync --since 30d`). Today the daemon reads whatever was modified in the last 7 days, and that window is not configurable.
|
|
743
|
+
- **`config get` / `config set` / `config edit`** — edit the YAML by hand for now.
|
|
744
|
+
- **`--verbose` logging** and per-run agent selection (`start --agent codex`); use `tracking.agents`.
|
|
745
|
+
- **Local time accounting.** Human-active / agent-active / idle windows are derived server-side from the event stream; `tracking.idle_timeout_seconds` is parsed by the collector but not used by it.
|
|
746
|
+
- **Process metrics.** `tracking.process_metrics` is accepted and ignored.
|
|
747
|
+
- **`session.ended`, `heartbeat`, `model.request` and `git.branch_changed`** are in the schema but no adapter emits them yet.
|
|
748
|
+
- **Local task classification** (`task_category`) — the field exists in the schema; the collector only derives a title.
|
|
749
|
+
- **Windows.** The service installer covers launchd and systemd only; `--foreground` works anywhere Node 24+ does.
|
|
750
|
+
- **Content-derived `event_id`.** Ids are random per enqueue, so retrying a batch is safe but re-reading a truncated transcript would create duplicates.
|
|
751
|
+
- **`MultiEdit`.** The Claude Code adapter derives file changes from `Edit`, `Write`, `NotebookEdit` and `Read`; a `MultiEdit` call is still recorded as `tool.started`/`tool.completed`, but produces no `file.changed` events and no line counts.
|
|
752
|
+
|
|
753
|
+
---
|
|
754
|
+
|
|
755
|
+
## Contributing
|
|
756
|
+
|
|
757
|
+
The single highest-value contribution is **a new agent adapter**, and it is smaller than it sounds:
|
|
758
|
+
one file implementing three methods — `detect()`, `health()`, `normalize()` — plus a redacted fixture
|
|
759
|
+
and a test. There is deliberately no `installHooks()`; if an agent cannot be observed by reading files
|
|
760
|
+
it already writes, open an issue before writing code.
|
|
761
|
+
|
|
762
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md) walks the whole thing: dev setup is `npm install && npm test`,
|
|
763
|
+
and running against a local API is one environment variable.
|
|
764
|
+
|
|
765
|
+
- 🐛 [Bug report](https://github.com/agentstrack/collector/issues/new?template=bug_report.yml)
|
|
766
|
+
- 💡 [Feature request](https://github.com/agentstrack/collector/issues/new?template=feature_request.yml)
|
|
767
|
+
- 🤖 [Request an agent](https://github.com/agentstrack/collector/issues/new?template=agent_support.yml)
|
|
768
|
+
- 🙋 [Getting help](./SUPPORT.md) — where a question goes versus a bug versus a vulnerability
|
|
769
|
+
- 🗺 [Roadmap](./ROADMAP.md) — the known gaps, and which are good first issues
|
|
770
|
+
- 🏛 [Governance](./GOVERNANCE.md) — how decisions get made and how to become a maintainer
|
|
771
|
+
- 🔒 [Security policy](./SECURITY.md) — report vulnerabilities privately
|
|
772
|
+
- 📜 [Code of Conduct](./CODE_OF_CONDUCT.md)
|
|
773
|
+
|
|
774
|
+
## License
|
|
775
|
+
|
|
776
|
+
[Apache License 2.0](./LICENSE) © AgentsTrack contributors.
|
|
777
|
+
|
|
778
|
+
The collector is open source and always will be. It is the part that runs on your machine and reads
|
|
779
|
+
your files — you should be able to audit every line of it.
|