@paxnax/berliner 0.5.3 → 0.7.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/README.md +1 -0
- package/dist/agents/claude.js +11 -8
- package/dist/agents/codex.js +14 -17
- package/dist/agents/opencode.js +6 -2
- package/dist/agents/read.js +50 -8
- package/dist/args.js +5 -0
- package/dist/client.js +4 -2
- package/dist/commands/body.js +11 -2
- package/dist/commands/hook.js +65 -53
- package/dist/commands/nudge.js +64 -0
- package/dist/commands/report.js +21 -6
- package/dist/commands/session.js +5 -2
- package/dist/commands/sessions.js +4 -3
- package/dist/commands/setup.js +4 -1
- package/dist/commands/summary.js +6 -5
- package/dist/commands/team.js +21 -0
- package/dist/commands/update.js +6 -2
- package/dist/commands/upgrade.js +4 -2
- package/dist/config.js +17 -2
- package/dist/gh.js +6 -4
- package/dist/index.js +14 -1
- package/dist/ledger.js +202 -0
- package/dist/lock.js +45 -0
- package/dist/output.js +5 -0
- package/dist/setup/hooks.js +28 -12
- package/dist/team.js +99 -0
- package/dist/tests/check.js +108 -0
- package/dist/tests/command.js +72 -0
- package/dist/tests/nudge.js +68 -0
- package/dist/tests/runs.js +195 -0
- package/dist/upload.js +1 -0
- package/opencode/berliner.js +45 -3
- package/package.json +10 -1
- package/skill/SKILL.md +33 -1
package/opencode/berliner.js
CHANGED
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
// - client.session.get({ path: { id } }) returns { data } with the server's Session, which holds cost, tokens and model,
|
|
11
11
|
// so the messages are not needed;
|
|
12
12
|
// - subagents run in child sessions (parentID set), each with its own cost and tokens, and
|
|
13
|
-
// client.session.children({ path: { id } }) returns { data } with a session's direct children
|
|
13
|
+
// client.session.children({ path: { id } }) returns { data } with a session's direct children;
|
|
14
|
+
// - client.session.messages({ path: { id } }) returns { data }, an array of { info, parts } (sdk/js/src/gen/types.gen.ts at
|
|
15
|
+
// v1.18.30: SessionMessagesData; an assistant info has role, time.created, cost and tokens). A fork's session row counts
|
|
16
|
+
// its parent's usage too, and its copied messages keep the parent's time.created, so `own` counts only newer ones.
|
|
14
17
|
import { spawn } from "node:child_process";
|
|
15
18
|
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
16
19
|
import { homedir } from "node:os";
|
|
@@ -37,13 +40,50 @@ async function topLevelId(client, id) {
|
|
|
37
40
|
return session && !session.parentID ? session.id : null;
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
const sum = (a, b) => ({
|
|
44
|
+
tokens: {
|
|
45
|
+
input: a.tokens.input + b.tokens.input,
|
|
46
|
+
output: a.tokens.output + b.tokens.output,
|
|
47
|
+
reasoning: a.tokens.reasoning + b.tokens.reasoning,
|
|
48
|
+
cache: { read: a.tokens.cache.read + b.tokens.cache.read, write: a.tokens.cache.write + b.tokens.cache.write },
|
|
49
|
+
},
|
|
50
|
+
cost: a.cost + b.cost,
|
|
51
|
+
});
|
|
52
|
+
const zero = () => ({ tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, cost: 0 });
|
|
53
|
+
|
|
54
|
+
// What the session itself used: its assistant messages created at or after the session was. null when they can't be listed.
|
|
55
|
+
async function ownUsage(client, session) {
|
|
56
|
+
try {
|
|
57
|
+
const messages = (await within(client.session.messages({ path: { id: session.id } }), 5000))?.data;
|
|
58
|
+
if (!Array.isArray(messages)) return null;
|
|
59
|
+
let own = zero();
|
|
60
|
+
for (const { info: m } of messages) {
|
|
61
|
+
if (m?.role !== "assistant" || num(m.time?.created) < num(session.time?.created)) continue;
|
|
62
|
+
const t = m.tokens;
|
|
63
|
+
own = sum(own, {
|
|
64
|
+
tokens: {
|
|
65
|
+
input: num(t?.input),
|
|
66
|
+
output: num(t?.output),
|
|
67
|
+
reasoning: num(t?.reasoning),
|
|
68
|
+
cache: { read: num(t?.cache?.read), write: num(t?.cache?.write) },
|
|
69
|
+
},
|
|
70
|
+
cost: num(m.cost),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return own;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
40
79
|
// The session with its subagents' cost and tokens added in, in the shape the CLI reads. OpenCode 1.18.30 keeps them apart:
|
|
41
80
|
// a session's cost and tokens grow only from its own step-finish parts (packages/core/src/session/projector.ts:310-326,
|
|
42
81
|
// applyUsage on the part's own sessionID, lines 89-109), and a live run with one task-tool subagent gave the parent 0.215
|
|
43
82
|
// and the child 1.05.
|
|
44
83
|
async function withChildren(client, session, depth = 0) {
|
|
45
84
|
const children = depth < MAX_DEPTH ? (await client.session.children({ path: { id: session.id } }))?.data : null;
|
|
46
|
-
|
|
85
|
+
const mine = await ownUsage(client, session);
|
|
86
|
+
if (!Array.isArray(children) || children.length === 0) return mine ? { ...session, own: mine } : session;
|
|
47
87
|
const t = session.tokens;
|
|
48
88
|
let cost = num(session.cost);
|
|
49
89
|
const tokens = {
|
|
@@ -52,7 +92,9 @@ async function withChildren(client, session, depth = 0) {
|
|
|
52
92
|
reasoning: num(t?.reasoning),
|
|
53
93
|
cache: { read: num(t?.cache?.read), write: num(t?.cache?.write) },
|
|
54
94
|
};
|
|
95
|
+
let own = mine;
|
|
55
96
|
for (const c of await Promise.all(children.map((child) => withChildren(client, child, depth + 1)))) {
|
|
97
|
+
own = own && c.own ? sum(own, c.own) : null;
|
|
56
98
|
cost += num(c.cost);
|
|
57
99
|
tokens.input += num(c.tokens?.input);
|
|
58
100
|
tokens.output += num(c.tokens?.output);
|
|
@@ -60,7 +102,7 @@ async function withChildren(client, session, depth = 0) {
|
|
|
60
102
|
tokens.cache.read += num(c.tokens?.cache?.read);
|
|
61
103
|
tokens.cache.write += num(c.tokens?.cache?.write);
|
|
62
104
|
}
|
|
63
|
-
return { ...session, cost, tokens };
|
|
105
|
+
return { ...session, cost, tokens, ...(own && { own }) };
|
|
64
106
|
}
|
|
65
107
|
|
|
66
108
|
// Each save's place in the order saves started, and per session the latest one written: a save that finishes after a later
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paxnax/berliner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Report coding-agent sessions to Berliner",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"codex",
|
|
8
|
+
"opencode",
|
|
9
|
+
"coding-agents",
|
|
10
|
+
"ai-cost-tracking",
|
|
11
|
+
"cli"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://berliner.app",
|
|
5
14
|
"type": "module",
|
|
6
15
|
"engines": {
|
|
7
16
|
"node": ">=22"
|
package/skill/SKILL.md
CHANGED
|
@@ -28,6 +28,7 @@ Hooks installed by `berliner setup` already report the session's cost, model, re
|
|
|
28
28
|
| `--pr` | Every pull request, one flag each. |
|
|
29
29
|
| `--coverage` | Percent, from the test run's coverage summary. |
|
|
30
30
|
| `--evidence` | Each screenshot or video. |
|
|
31
|
+
| `--team`, `--no-team` | Only when the user asks for a one-off. See [Teams](#teams). |
|
|
31
32
|
|
|
32
33
|
Do not pass `--session-id`, `--cost` or `--model`: the CLI fills them in from the tool's own records, and an explicit value is ignored with a warning. `--repo` and `--plan` are not needed either: the plan comes from the user's `BERLINER_PLAN`, or from their subscriptions in Settings. A report without `--title` keeps the session's existing title. `berliner update` ignores a session ID here.
|
|
33
34
|
|
|
@@ -86,6 +87,36 @@ In a non-interactive session (no user to answer), never ask and never save.
|
|
|
86
87
|
|
|
87
88
|
The repository comes from `git remote get-url origin`; `--repo owner/name` names another one.
|
|
88
89
|
|
|
90
|
+
## Teams
|
|
91
|
+
|
|
92
|
+
A session reports to the user's own plan unless a team is set. The CLI picks the team from the first of these that exists:
|
|
93
|
+
|
|
94
|
+
| Source | Use it for |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `--team <uuid>` or `--no-team` | One report. `--no-team` beats every other source. |
|
|
97
|
+
| `team` in `.berliner.json` at the repository root: `{ "team": "<uuid>" }` | The whole team. The file is meant to be committed. |
|
|
98
|
+
| `BERLINER_TEAM` | One machine or shell. |
|
|
99
|
+
| `berliner team <uuid>` | One machine, saved globally. `berliner team --none` clears it; `berliner team` prints the team in effect and where it came from. |
|
|
100
|
+
|
|
101
|
+
With none of them, the session goes to the user's own plan. Do not set a team yourself: the user or the repository already did.
|
|
102
|
+
|
|
103
|
+
Only the report that creates a session sends the team. A session's team is fixed after that, and the CLI warns when a later setting differs. Ignore the warning.
|
|
104
|
+
|
|
105
|
+
The server can refuse the report that creates a team session with a 422. The message says how to fix it:
|
|
106
|
+
|
|
107
|
+
| Code | Meaning | Fix |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `team_not_found` | No team has that uuid. | Check the uuid in `.berliner.json`, `BERLINER_TEAM` or the global setting. |
|
|
110
|
+
| `team_not_member` | The user is not a member of the team. | The user accepts the team's invite in the web app, or the owner invites them. |
|
|
111
|
+
| `team_not_eligible` | The repository's owner is not an organization the team covers. | The team's owner adds the organization in the web app, or the user reports this repository without the team. |
|
|
112
|
+
| `team_inactive` | The team has no active plan. | The team's owner sets up the plan in the web app. |
|
|
113
|
+
|
|
114
|
+
When a report is refused, tell the user in your next message and quote the message the CLI printed. Inside Claude Code, Codex or OpenCode the hook cannot print it: it saves a note, and the next `berliner report`, `update`, `session` or `sessions` in that repository prints it once. The web app shows it as a banner too.
|
|
115
|
+
|
|
116
|
+
Never add `--no-team` or a different `--team` just to get past a refusal unless the user agrees. That moves the session to another plan.
|
|
117
|
+
|
|
118
|
+
The team's owner sees every session and file reported to the team. Members never see each other's team work.
|
|
119
|
+
|
|
89
120
|
## Keep PR states current
|
|
90
121
|
|
|
91
122
|
A PR in a repository without the Berliner GitHub App keeps the state you last reported. So:
|
|
@@ -115,10 +146,11 @@ Report again with the same `--session-id` whenever something changes.
|
|
|
115
146
|
- `--commits <n>` (outside the tools; the hooks count them): `git rev-list --count <base>..HEAD`
|
|
116
147
|
- `--coverage <percent>`: from the test run's coverage summary.
|
|
117
148
|
- `--pr <url>[,merge=<open|merged|closed>][,ci=<pending|running|passed|failed|none>]`: repeat per PR. URL from `gh pr view --json url`.
|
|
118
|
-
- `--evidence <path>[::caption]`: repeat per screenshot or video.
|
|
149
|
+
- `--evidence <path>[::caption[::happy|unhappy|edge]]`: repeat per screenshot or video; the optional last part says whether it shows the happy path, an unhappy path or an edge case. A caption can't contain `::`.
|
|
119
150
|
- `--public`: with `--evidence`, also make those files viewable without sign-in. See below.
|
|
120
151
|
- `--task <id>`, `--task-url <url>`: the ticket. See [Tickets](#tickets).
|
|
121
152
|
- `--project <name>`: only the ticket's tracker project or a project the user named. See [Projects](#projects).
|
|
153
|
+
- `--team <uuid>`, `--no-team`: which plan the session reports to. See [Teams](#teams).
|
|
122
154
|
- `--started-at <ISO>`, `--ended-at <ISO>`.
|
|
123
155
|
|
|
124
156
|
Quote any value that contains spaces.
|