@marginfront/code-cost-clarity 0.5.4 → 0.6.1
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 +62 -21
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +1848 -1636
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> See your Claude Code **and Codex** spend in MarginFront. One command wires your
|
|
4
4
|
> coding-agent usage telemetry through a local collector and into MarginFront,
|
|
5
|
-
> priced per
|
|
5
|
+
> priced per developer, per model, with **accurate prompt-cache token splitting**.
|
|
6
6
|
> One package, three modes: Claude Code only, Codex only, or both.
|
|
7
7
|
|
|
8
8
|
> **⚠️ Beta (pilot software).** Under active development - pin a version, expect rough
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
**📖 Full documentation:** https://docs.marginfront.com/tools/code-cost-clarity
|
|
18
18
|
|
|
19
19
|
**This is internal cost visibility, not billing and not a spend cap.** The
|
|
20
|
-
installing company watches its own AI coding spend (per
|
|
21
|
-
R&D-vs-COGS). It does not charge
|
|
20
|
+
installing company watches its own AI coding spend (per developer, per model,
|
|
21
|
+
R&D-vs-COGS). It does not charge developers and it does not cut anyone off.
|
|
22
22
|
|
|
23
23
|
**Your LLM keys stay yours.** This tool never reads, needs, or transmits your
|
|
24
24
|
Anthropic or OpenAI API key. The only credential it uses is your MarginFront key
|
|
@@ -28,7 +28,7 @@ Anthropic or OpenAI API key. The only credential it uses is your MarginFront key
|
|
|
28
28
|
|
|
29
29
|
## What this does (one sentence)
|
|
30
30
|
|
|
31
|
-
Every time
|
|
31
|
+
Every time a developer runs Claude Code, this connector captures the token usage
|
|
32
32
|
and sends it to MarginFront as a usage event, so you can see who used what, on
|
|
33
33
|
which model, and how much it cost.
|
|
34
34
|
|
|
@@ -37,13 +37,13 @@ which model, and how much it cost.
|
|
|
37
37
|
Think of it like a cash-register receipt system:
|
|
38
38
|
|
|
39
39
|
1. **Claude Code** is the register. As it works, it broadcasts receipts
|
|
40
|
-
(OpenTelemetry exports): how many tokens, which model, which
|
|
40
|
+
(OpenTelemetry exports): how many tokens, which model, which developer.
|
|
41
41
|
2. **The collector** (`otelcol-contrib`, open source) is the catcher. It runs in
|
|
42
42
|
the background, catches the receipts, and writes them to a file.
|
|
43
43
|
3. **The forwarder** (our glue, inside this package) reads those receipts and
|
|
44
44
|
sends each one to MarginFront.
|
|
45
45
|
4. **MarginFront** records the event, prices it, and shows it under the
|
|
46
|
-
|
|
46
|
+
developer's email.
|
|
47
47
|
|
|
48
48
|
```
|
|
49
49
|
Claude Code (your task)
|
|
@@ -103,7 +103,7 @@ claude # or: codex (or open the Claude/Codex desktop apps)
|
|
|
103
103
|
> config when they launch, so anything that was already open won't emit until it's reopened.
|
|
104
104
|
|
|
105
105
|
That's it. No `source`, no second terminal. Your spend shows up in MarginFront under
|
|
106
|
-
your
|
|
106
|
+
your developer email, automatically, and the background meter restarts itself at
|
|
107
107
|
every login. Check on it anytime:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
@@ -115,7 +115,7 @@ npx @marginfront/code-cost-clarity status
|
|
|
115
115
|
stops it). You'll see a line per turn like:
|
|
116
116
|
|
|
117
117
|
```
|
|
118
|
-
[14:22:07] recorded
|
|
118
|
+
[14:22:07] recorded developer@example.com · in=3210 out=287 · server=$0.0232 · cc=$0.0236 event=9f0c2a71-...
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
**CI / scripted installs:** add `--no-prompt` to `init` to skip BOTH questions (the
|
|
@@ -180,34 +180,75 @@ It does **not** capture:
|
|
|
180
180
|
|
|
181
181
|
---
|
|
182
182
|
|
|
183
|
-
## Per-
|
|
183
|
+
## Per-developer attribution is automatic
|
|
184
184
|
|
|
185
185
|
What makes "who spent what" work is `user.email`, and **Claude Code puts it in
|
|
186
|
-
the telemetry on its own**. It's the
|
|
187
|
-
There is no manual email config. Each
|
|
186
|
+
the telemetry on its own**. It's the developer's logged-in Claude account email.
|
|
187
|
+
There is no manual email config. Each developer loads the telemetry settings
|
|
188
188
|
and runs Claude Code normally.
|
|
189
189
|
|
|
190
|
-
**Works whether the
|
|
190
|
+
**Works whether the developer signs in with an org-managed seat or an interactive
|
|
191
191
|
login.** On org-managed Claude seats the email is stamped for free. If an
|
|
192
|
-
|
|
192
|
+
developer's sign-in doesn't surface an email, the connector doesn't drop the
|
|
193
193
|
usage. It attributes it to a clearly labeled placeholder customer
|
|
194
194
|
(`claude-code-no-identity`) and prints how to fix it (sign in with an org-managed
|
|
195
195
|
seat, or attach a customer mapping). You'll see the placeholder in `preview`/`run`
|
|
196
196
|
output if it ever kicks in.
|
|
197
197
|
|
|
198
|
-
> The MarginFront **API key** is separate from the
|
|
198
|
+
> The MarginFront **API key** is separate from the developer's identity: it's the
|
|
199
199
|
> connector's own credential for posting to MarginFront. `run` needs it;
|
|
200
200
|
> `preview` does not.
|
|
201
201
|
|
|
202
202
|
---
|
|
203
203
|
|
|
204
|
+
## Shared one login across the team? Set a per-machine developer (`CCC_DEVELOPER_EMAIL`)
|
|
205
|
+
|
|
206
|
+
Some teams share **one** Claude/Codex login across the whole team. When that
|
|
207
|
+
happens, every developer's telemetry carries the **same** `user.email`, so all the
|
|
208
|
+
AI cost collapses onto one person and per-developer attribution breaks. CCC fixes
|
|
209
|
+
this without any change to how you log in, because **CCC runs locally on each
|
|
210
|
+
laptop** - so each laptop can carry its own developer identity.
|
|
211
|
+
|
|
212
|
+
**During `init` we ask one question:** which developer this machine's AI cost should
|
|
213
|
+
be attributed to. The prompt is pre-filled with your **global git email**
|
|
214
|
+
(`git config --global user.email`), which is usually the right person sitting at
|
|
215
|
+
this laptop. You have three choices:
|
|
216
|
+
|
|
217
|
+
- **Press Enter** to accept the pre-filled email.
|
|
218
|
+
- **Type a different email** to attribute this machine to someone else.
|
|
219
|
+
- **Clear it and leave it blank** to keep the old behavior - auto-detect from
|
|
220
|
+
whatever email the coding-agent login reports.
|
|
221
|
+
|
|
222
|
+
Whatever you choose is saved as **`CCC_DEVELOPER_EMAIL`** in
|
|
223
|
+
`~/.marginfront-ccc/.env` (mode 600, right next to your key). To change it later,
|
|
224
|
+
edit that one line and then `stop` + `start` (or just re-run `init`).
|
|
225
|
+
|
|
226
|
+
**Precedence at send time (highest wins):**
|
|
227
|
+
|
|
228
|
+
1. **`CCC_DEVELOPER_EMAIL`** (this machine's developer) - if set, it wins for **every**
|
|
229
|
+
record this machine sends, Claude **and** Codex, token turns **and** tool calls.
|
|
230
|
+
2. Otherwise the email the coding-agent login reports (`user.email`).
|
|
231
|
+
3. Otherwise the no-identity placeholder (`claude-code-no-identity` /
|
|
232
|
+
`codex-no-identity`).
|
|
233
|
+
|
|
234
|
+
If you **don't** set it (leave it blank), nothing changes - attribution is
|
|
235
|
+
byte-for-byte what it was before this feature: the login's own `user.email`, then
|
|
236
|
+
the placeholder.
|
|
237
|
+
|
|
238
|
+
> This is **internal cost visibility only**. `CCC_DEVELOPER_EMAIL` re-labels which
|
|
239
|
+
> developer the cost shows up under in _your_ MarginFront - it does **not** change
|
|
240
|
+
> who pays or any customer's bill. The `--no-prompt` (CI / scripted) install skips
|
|
241
|
+
> this question and leaves `CCC_DEVELOPER_EMAIL` unset.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
204
245
|
## Privacy (read this)
|
|
205
246
|
|
|
206
247
|
This tool is **internal cost visibility**, and being honest about what it turns on
|
|
207
248
|
matters, so here is the full picture in plain English.
|
|
208
249
|
|
|
209
|
-
**Your
|
|
210
|
-
the whole point: per-
|
|
250
|
+
**Your developer email travels in the telemetry, in plaintext, on purpose.** That's
|
|
251
|
+
the whole point: per-developer cost attribution needs to know who ran the turn.
|
|
211
252
|
Claude Code stamps `user.email` (your logged-in account email) onto the usage
|
|
212
253
|
telemetry, the local collector writes it to a file on your machine, and the
|
|
213
254
|
forwarder POSTs it to MarginFront so the spend lands under your name. It is **not**
|
|
@@ -304,7 +345,7 @@ silent charge). Free built-ins (file reads, shell, grep) are never forwarded.
|
|
|
304
345
|
|
|
305
346
|
The same package captures **Codex** too. Nothing extra to install. Codex reports
|
|
306
347
|
to the **same** local collector. You get three modes for free: Claude Code only,
|
|
307
|
-
Codex only, or **both** (same
|
|
348
|
+
Codex only, or **both** (same developer's name on all of it).
|
|
308
349
|
|
|
309
350
|
**`init` turns it on for you** when you consent, it adds this `[otel]` block to
|
|
310
351
|
your `~/.codex/config.toml` automatically:
|
|
@@ -350,8 +391,8 @@ the input rate and drops the cache-read field, so the number reads high, never l
|
|
|
350
391
|
|
|
351
392
|
### Identity by sign-in mode (org seat vs ChatGPT login)
|
|
352
393
|
|
|
353
|
-
Per-
|
|
354
|
-
as Claude Code. Whether that email surfaces depends on how the
|
|
394
|
+
Per-developer attribution rides on `user.email` from Codex's telemetry, same idea
|
|
395
|
+
as Claude Code. Whether that email surfaces depends on how the developer signs in
|
|
355
396
|
to Codex (an org-managed or API-key sign-in stamps it; some interactive ChatGPT
|
|
356
397
|
logins may not). We never read that OpenAI key; the only thing that matters here is
|
|
357
398
|
whether the telemetry carries an email. If it doesn't surface, the usage isn't
|
|
@@ -375,7 +416,7 @@ exactly like the Claude path.
|
|
|
375
416
|
|
|
376
417
|
> **Confirm the dollar figure once.** The exact Codex dollar figure should be
|
|
377
418
|
> confirmed against one real captured session: that Codex reports per-turn (not
|
|
378
|
-
> session-cumulative) token counts on the log stream, and that your
|
|
419
|
+
> session-cumulative) token counts on the log stream, and that your developer email
|
|
379
420
|
> populates under your sign-in mode. The mapping above is the verified-safe
|
|
380
421
|
> default; the confirmation is a one-session spike, not a blocker to installing.
|
|
381
422
|
|
|
@@ -454,7 +495,7 @@ curl -s -H "x-api-key: $KEY" \
|
|
|
454
495
|
|
|
455
496
|
---
|
|
456
497
|
|
|
457
|
-
## For
|
|
498
|
+
## For developers (technical appendix)
|
|
458
499
|
|
|
459
500
|
**Input shape:** OTLP/JSON, tree `resourceMetrics[].scopeMetrics[].metrics[]`. Two
|
|
460
501
|
metrics matter: `claude_code.token.usage` (one datapoint per `type` in
|
package/dist/cli.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ interface PromptIO {
|
|
|
33
33
|
terminal?: boolean;
|
|
34
34
|
}
|
|
35
35
|
declare function promptYesNo(promptText: string, io?: PromptIO, defaultYes?: boolean): Promise<boolean>;
|
|
36
|
+
declare function promptVisible(promptText: string, defaultValue?: string, io?: PromptIO): Promise<string>;
|
|
36
37
|
interface InitDeps {
|
|
37
38
|
ensureConfigFiles?: () => void;
|
|
38
39
|
ensureCollectorBinary?: () => void;
|
|
@@ -40,6 +41,9 @@ interface InitDeps {
|
|
|
40
41
|
isTTY?: () => boolean;
|
|
41
42
|
promptSecret?: (promptText: string) => Promise<string>;
|
|
42
43
|
writeApiKey?: (value: string) => void;
|
|
44
|
+
gitDefaultEmail?: () => string;
|
|
45
|
+
promptDeveloperEmail?: (defaultValue: string) => Promise<string>;
|
|
46
|
+
writeDeveloperEmail?: (value: string) => void;
|
|
43
47
|
hasApiKey?: () => boolean;
|
|
44
48
|
promptConsent?: () => Promise<boolean>;
|
|
45
49
|
writeClaudeTelemetry?: () => WriteClaudeSettingsResult;
|
|
@@ -83,8 +87,13 @@ interface UninstallDeps {
|
|
|
83
87
|
purgeConfigDir?: () => void;
|
|
84
88
|
findZshrcLines?: () => ZshrcSourceLineResult;
|
|
85
89
|
unsetDesktopEnv?: () => void;
|
|
90
|
+
readForwarderPid?: () => number | null;
|
|
91
|
+
isForwarderAlive?: (pid: number) => boolean;
|
|
92
|
+
signalForwarder?: (pid: number, signal: "SIGTERM" | "SIGKILL") => void;
|
|
93
|
+
sleep?: (ms: number) => Promise<void>;
|
|
94
|
+
now?: () => number;
|
|
86
95
|
log?: (message: string) => void;
|
|
87
96
|
}
|
|
88
|
-
declare function cmdUninstall(purge: boolean, deps?: UninstallDeps): number
|
|
97
|
+
declare function cmdUninstall(purge: boolean, deps?: UninstallDeps): Promise<number>;
|
|
89
98
|
|
|
90
|
-
export { type InitDeps, type PromptIO, type StartDeps, type StopDeps, type UninstallDeps, cmdInit, cmdRun, cmdStart, cmdUninstall, promptYesNo, runningForwarderPid, stopForwarderThenStopCollector };
|
|
99
|
+
export { type InitDeps, type PromptIO, type StartDeps, type StopDeps, type UninstallDeps, cmdInit, cmdRun, cmdStart, cmdUninstall, promptVisible, promptYesNo, runningForwarderPid, stopForwarderThenStopCollector };
|