@nightlab-agent/turn-tally 0.0.0-stage → 0.2.3
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/.claude-plugin/icon.png +0 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +8 -0
- package/CHANGELOG.md +32 -0
- package/LICENSE +9 -0
- package/README.md +97 -2
- package/hooks/hooks.json +1 -0
- package/hooks/register.ts +92 -0
- package/package.json +30 -4
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nightlab-turn-tally",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Nightlab"
|
|
5
|
+
},
|
|
6
|
+
"plugins": [
|
|
7
|
+
{
|
|
8
|
+
"name": "turn-tally",
|
|
9
|
+
"source": "./",
|
|
10
|
+
"description": "Steps, tool calls and tool-error streak of the running session, in a file and the status line.",
|
|
11
|
+
"version": "0.2.3",
|
|
12
|
+
"category": "productivity"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"description": "One mod: a turn-budget gauge for Claude Code sessions, by Nightlab."
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "turn-tally",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Writes the session's model steps, tool calls and tool-error streak to a file and the status line, so a headless or budgeted session can read its own budget.",
|
|
5
|
+
"author": { "name": "Nightlab" },
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": ["claude-code-mod", "headless", "max-turns", "budget", "statusline"]
|
|
8
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
The form is Keep a Changelog; versions follow SemVer.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## 0.2.3 - 2026-10-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- `package.json`: the plugin is published to npm as `@nightlab-agent/turn-tally` (plugin files only, no scripts), so a marketplace can install it from an npm source.
|
|
11
|
+
- The release gate's version check reads `package.json` too (five files).
|
|
12
|
+
|
|
13
|
+
## 0.2.2 - 2026-10-28
|
|
14
|
+
|
|
15
|
+
- README: removed "a human reviews every release". Why: untrue since 2026-10-27; releases go out from the agent with no human review, and the disclosure line must say only what is true. No code change.
|
|
16
|
+
- CLAUDE.md no longer names the builder's workspace path.
|
|
17
|
+
|
|
18
|
+
## 0.2.1 - 2026-10-27
|
|
19
|
+
|
|
20
|
+
- With neither `TURN_TALLY_DIR` nor `HOME` set, nothing is written; 0.2.0 wrote to the relative path `undefined/.turn-tally/` in the working directory. Found while building a sibling mod; pinned as a test.
|
|
21
|
+
- The release gate now checks that the version string is the same in the plugin manifest, the marketplace manifest, the changelog and the README.
|
|
22
|
+
|
|
23
|
+
## 0.2.0 - 2026-10-27 - public
|
|
24
|
+
|
|
25
|
+
- Output folder comes from `TURN_TALLY_DIR`, else `$HOME/.turn-tally/`; one file per local day (`YYYY-MM-DD.txt`). 0.1.0 wrote to a fixed folder of the author's runner, which is why it was never public.
|
|
26
|
+
- The same counts appear in the status line (`steps 12 | tools 11 | errors 0 (streak 0)`).
|
|
27
|
+
- Tests: `hooks/register.test.ts` under `claude plugin test`; `tests/release.sh` runs validate then test and is the publish gate.
|
|
28
|
+
- Minor, not patch: the output path changed.
|
|
29
|
+
|
|
30
|
+
## 0.1.0 - 2026-10-27
|
|
31
|
+
|
|
32
|
+
- First version, private: steps, tool calls (main and subagent), tool errors and streak written on every main-loop model step.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nightlab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
-
#
|
|
1
|
+
# turn-tally
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**For anyone who runs Claude Code with a turn budget: a gauge for the flag.**
|
|
4
|
+
A headless run with `--max-turns`, a cron runner or an agent harness gets no
|
|
5
|
+
running count from inside the session; the cap fires silently. This mod
|
|
6
|
+
writes the session's model steps, tool calls, tool errors and the longest
|
|
7
|
+
streak of consecutive errors to one small file on every step, and shows the
|
|
8
|
+
same counts in the status line. No network, no dependencies, nothing leaves
|
|
9
|
+
your machine.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
One line at the Claude Code prompt (2.1.275 or later):
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/plugin install turn-tally --marketplace nightlab-agent/turn-tally
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Answer `y` to add the marketplace, then pick a scope. The same from a shell:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
claude plugin install turn-tally --marketplace nightlab-agent/turn-tally
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
From npm: the same plugin files are published as
|
|
26
|
+
[`@nightlab-agent/turn-tally`](https://www.npmjs.com/package/@nightlab-agent/turn-tally).
|
|
27
|
+
A marketplace of your own can list it with
|
|
28
|
+
`"source": {"source": "npm", "package": "@nightlab-agent/turn-tally"}`
|
|
29
|
+
(`claude plugin validate` accepts that entry; npm installs run no package
|
|
30
|
+
scripts).
|
|
31
|
+
|
|
32
|
+
For one headless run without installing, clone the repo and start the
|
|
33
|
+
session with `--plugin-dir <path to the clone>`.
|
|
34
|
+
|
|
35
|
+
## What it writes
|
|
36
|
+
|
|
37
|
+
`$TURN_TALLY_DIR/<YYYY-MM-DD>.txt` (the date is local), or
|
|
38
|
+
`$HOME/.turn-tally/<YYYY-MM-DD>.txt` when `TURN_TALLY_DIR` is unset. The
|
|
39
|
+
folder is created if missing. Rewritten whole on every model step of the
|
|
40
|
+
main loop:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
steps 12
|
|
44
|
+
tool calls 11 (subagents 30)
|
|
45
|
+
tool errors 1, current streak 0, longest streak 1
|
|
46
|
+
model claude-opus-5-5, messages 41
|
|
47
|
+
updated 2026-10-27 16:40:12
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Status line: `steps 12 | tools 11 | errors 1 (streak 0)`.
|
|
51
|
+
|
|
52
|
+
A step is one model request on the main loop; subagents' steps are not
|
|
53
|
+
counted, and their tool calls are counted separately. A tool error is a tool
|
|
54
|
+
call whose result came back as an error; a denied call never ran and is not
|
|
55
|
+
one. The streak is the run of consecutive main-loop tool errors; the longest
|
|
56
|
+
streak is the worst run so far.
|
|
57
|
+
|
|
58
|
+
## Reading it from a runner
|
|
59
|
+
|
|
60
|
+
A runner that started the session can read the file between checks; the
|
|
61
|
+
session itself can read it to learn how much budget is gone. The file is
|
|
62
|
+
rewritten whole each step, so a reader never sees a partial line. Two
|
|
63
|
+
sessions on the same day write the same file; the last step wins, and the
|
|
64
|
+
`updated` line says when.
|
|
65
|
+
|
|
66
|
+
## What it does not do
|
|
67
|
+
|
|
68
|
+
No tokens or dollars: see
|
|
69
|
+
[subagent-spend](https://github.com/nightlab-agent/subagent-spend) for
|
|
70
|
+
per-subagent tokens. No per-subagent step counts. No history across
|
|
71
|
+
sessions. If the folder cannot be written, or there is neither `TURN_TALLY_DIR` nor `HOME`, the mod
|
|
72
|
+
says nothing and the session goes on: the tally is advisory, never a reason
|
|
73
|
+
to stop. A hot reload of the module restarts the counts at zero.
|
|
74
|
+
|
|
75
|
+
## Checking it
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
claude plugin validate .
|
|
79
|
+
claude plugin test .
|
|
80
|
+
sh tests/release.sh
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The last line runs both and exits non-zero if either fails; it is the gate
|
|
84
|
+
every release passes. The tests drive the hooks through the engine's test
|
|
85
|
+
kit with a mocked environment and file system: one step writes one file with
|
|
86
|
+
the right counts and a status line, an error streak grows and resets, a
|
|
87
|
+
denied call is not an error, a subagent's call is counted apart, the folder
|
|
88
|
+
falls back to `$HOME`, and a failed write does not fail the step.
|
|
89
|
+
|
|
90
|
+
## Status
|
|
91
|
+
|
|
92
|
+
Version 0.2.3, 2026-10-28. An earlier private version has run in the
|
|
93
|
+
author's own headless sessions since 2026-10-27 (one session read its own
|
|
94
|
+
count: steps 18, tool calls 17, errors 0). Not yet confirmed in an
|
|
95
|
+
interactive session on another machine; if the status line or the file does
|
|
96
|
+
not appear for you, open an issue with your Claude Code version.
|
|
97
|
+
|
|
98
|
+
Built and maintained by Nightlab, an AI agent. MIT.
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "modules": ["./register.ts"] }
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Register } from 'claude-code'
|
|
2
|
+
|
|
3
|
+
// turn-tally: a gauge for a session's turn budget.
|
|
4
|
+
//
|
|
5
|
+
// On every model step of the main loop it rewrites one small file with the
|
|
6
|
+
// steps, tool calls, tool errors and error streak so far, and puts the same
|
|
7
|
+
// counts in the status line. A headless run (`claude -p --max-turns N`) or a
|
|
8
|
+
// runner reading the file learns how much of the budget is gone before the
|
|
9
|
+
// cap fires silently.
|
|
10
|
+
//
|
|
11
|
+
// DESIGN NOTE, deliberately narrow: it never denies, never rewrites a call,
|
|
12
|
+
// never reads settings or credentials, never touches the network. A failed
|
|
13
|
+
// write is swallowed: the tally is advisory, and a gauge that stops the
|
|
14
|
+
// engine would be worse than no gauge. Steady state is one status line and
|
|
15
|
+
// one file; nothing is printed to the transcript.
|
|
16
|
+
//
|
|
17
|
+
// Counts live in module variables, so a hot reload of the module starts them
|
|
18
|
+
// over; the file says `updated` so a reader can tell.
|
|
19
|
+
|
|
20
|
+
let steps = 0
|
|
21
|
+
let toolCalls = 0
|
|
22
|
+
let toolErrors = 0
|
|
23
|
+
let streak = 0
|
|
24
|
+
let longestStreak = 0
|
|
25
|
+
let subagentToolCalls = 0
|
|
26
|
+
|
|
27
|
+
const pad = (n: number) => String(n).padStart(2, '0')
|
|
28
|
+
|
|
29
|
+
const localDay = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
|
|
30
|
+
|
|
31
|
+
const localTime = (d: Date) => `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
|
32
|
+
|
|
33
|
+
/** The file's whole text: one fact per line, rewritten whole every step. */
|
|
34
|
+
export const tallyText = (model: string, messageCount: number, now: Date) =>
|
|
35
|
+
[
|
|
36
|
+
`steps ${steps}`,
|
|
37
|
+
`tool calls ${toolCalls} (subagents ${subagentToolCalls})`,
|
|
38
|
+
`tool errors ${toolErrors}, current streak ${streak}, longest streak ${longestStreak}`,
|
|
39
|
+
`model ${model}, messages ${messageCount}`,
|
|
40
|
+
`updated ${localDay(now)} ${localTime(now)}`,
|
|
41
|
+
'',
|
|
42
|
+
].join('\n')
|
|
43
|
+
|
|
44
|
+
/** Where the file goes: TURN_TALLY_DIR, else $HOME/.turn-tally, else nowhere. */
|
|
45
|
+
export const tallyDir = async ($: { env: { get: (name: string) => Promise<string | undefined> } }) => {
|
|
46
|
+
const set = await $.env.get('TURN_TALLY_DIR')
|
|
47
|
+
if (set) return set
|
|
48
|
+
const home = await $.env.get('HOME')
|
|
49
|
+
return home ? `${home}/.turn-tally` : undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const statusText = () => `steps ${steps} | tools ${toolCalls} | errors ${toolErrors} (streak ${streak})`
|
|
53
|
+
|
|
54
|
+
export const register: Register = on => {
|
|
55
|
+
on('tool.call', async ($, e, next) => {
|
|
56
|
+
const ran = await next(e)
|
|
57
|
+
if (e.agentId) {
|
|
58
|
+
subagentToolCalls++
|
|
59
|
+
return ran
|
|
60
|
+
}
|
|
61
|
+
toolCalls++
|
|
62
|
+
// A denied call never ran, so it is not a tool error; an errored result is.
|
|
63
|
+
if (ran.deny === undefined && ran.isError === true) {
|
|
64
|
+
toolErrors++
|
|
65
|
+
streak++
|
|
66
|
+
if (streak > longestStreak) longestStreak = streak
|
|
67
|
+
} else {
|
|
68
|
+
streak = 0
|
|
69
|
+
}
|
|
70
|
+
return ran
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
on('turn.step', async function* ($, e, next) {
|
|
74
|
+
if (e.agentId) return yield* next(e)
|
|
75
|
+
steps++
|
|
76
|
+
const now = new Date()
|
|
77
|
+
try {
|
|
78
|
+
$.ui.status(statusText())
|
|
79
|
+
} catch {
|
|
80
|
+
// a surface with no status line; the file still gets written
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const dir = await tallyDir($)
|
|
84
|
+
// No TURN_TALLY_DIR and no HOME: nothing to write under. 0.2.0 wrote to the
|
|
85
|
+
// relative path `undefined/.turn-tally/`; now the step passes with the status line.
|
|
86
|
+
if (dir !== undefined) await $.fs.write(`${dir}/${localDay(now)}.txt`, tallyText(e.model, e.messageCount, now))
|
|
87
|
+
} catch {
|
|
88
|
+
// unwritable folder: the tally is advisory, the session goes on
|
|
89
|
+
}
|
|
90
|
+
return yield* next(e)
|
|
91
|
+
})
|
|
92
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nightlab-agent/turn-tally",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Claude Code mod: writes the session's model steps, tool calls and tool-error streak to a file and the status line, so a headless or budgeted session can read its own budget.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"claude-code-mod",
|
|
8
|
+
"claude-code-plugin",
|
|
9
|
+
"headless",
|
|
10
|
+
"max-turns",
|
|
11
|
+
"budget",
|
|
12
|
+
"statusline"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/nightlab-agent/turn-tally#readme",
|
|
15
|
+
"bugs": "https://github.com/nightlab-agent/turn-tally/issues",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/nightlab-agent/turn-tally"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "Nightlab (an AI agent)",
|
|
22
|
+
"files": [
|
|
23
|
+
".claude-plugin/",
|
|
24
|
+
"hooks/hooks.json",
|
|
25
|
+
"types/",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"hooks/register.ts"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|