@rosh100yx/outlier 0.4.24 → 0.4.25
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 +21 -3
- package/bin/outlier.js +96 -27
- package/bin/postinstall.js +18 -17
- package/package.json +1 -1
- package/src/carbon.ts +47 -10
- package/src/cli.ts +55 -20
package/README.md
CHANGED
|
@@ -50,9 +50,27 @@
|
|
|
50
50
|
└───────────┘
|
|
51
51
|
```
|
|
52
52
|
**Step 1:** Developer delegates code generation to an AI agent (Claude Code, Cursor).
|
|
53
|
-
**Step 2:**
|
|
54
|
-
**Step 3:**
|
|
55
|
-
**Step 4:**
|
|
53
|
+
**Step 2:** `outlier` reads the local trace — git history + AI logs — already on the machine.
|
|
54
|
+
**Step 3:** It reports who wrote the code, what it cost, and your authorship limit.
|
|
55
|
+
**Step 4:** Optionally, a local git hook *warns* (never silently blocks) when AI authorship exceeds your limit, so you review before you merge.
|
|
56
|
+
|
|
57
|
+
### What it reads (and what it doesn't)
|
|
58
|
+
|
|
59
|
+
`outlier` is local-first. It reads, from your own machine, only:
|
|
60
|
+
|
|
61
|
+
- **`git log`** of the current repo — to count commits carrying a `Co-Authored-By` trailer (the AI-authorship share).
|
|
62
|
+
- **Your Claude Code session transcripts** at `~/.claude/projects/<this-repo>/*.jsonl` — to sum token usage for the cost / cache / carbon estimate. (Falls back to `~/.claude/tokenomics-log.jsonl` if present.)
|
|
63
|
+
|
|
64
|
+
It does **not** send anything anywhere — no API calls, no telemetry, no account. Your code and prompts never leave the machine. The only network action in the whole tool is *you* choosing to open a share link or a feedback issue.
|
|
65
|
+
|
|
66
|
+
### How accurate is it?
|
|
67
|
+
|
|
68
|
+
We are deliberately honest about this:
|
|
69
|
+
|
|
70
|
+
- **Authorship** is an exact count of trailer-tagged commits, but it is a *proxy* for real human-vs-AI effort, and it **under-counts** when your agent doesn't write the `Co-Authored-By` trailer. A surprisingly low number usually means missing trailers, not that you wrote everything.
|
|
71
|
+
- **Tokens** are exact when the transcripts are present; otherwise the cost/carbon section reads zero.
|
|
72
|
+
- **Cost ($)** is exact when the log carries a cost field, otherwise a *rough* blended token estimate (labelled as such).
|
|
73
|
+
- **Carbon** is a rough estimate (inference energy varies ~4–20× in the literature) and the per-region figure is a *counterfactual* — cloud inference runs on the provider's grid, not yours. Treat it as an order-of-magnitude signal, not an audit.
|
|
56
74
|
|
|
57
75
|
## What Outlier Adds
|
|
58
76
|
`outlier` builds a coordination layer on top of native agent workflows.
|
package/bin/outlier.js
CHANGED
|
@@ -165,7 +165,7 @@ var require_picocolors = __commonJS((exports, module) => {
|
|
|
165
165
|
var require_package = __commonJS((exports, module) => {
|
|
166
166
|
module.exports = {
|
|
167
167
|
name: "@rosh100yx/outlier",
|
|
168
|
-
version: "0.4.
|
|
168
|
+
version: "0.4.25",
|
|
169
169
|
description: "AI Code Governance & Capability Auditing for the Terminal. Measures AI reliance, context waste, and enforces local CI/CD policies.",
|
|
170
170
|
bin: {
|
|
171
171
|
outlier: "bin/outlier.js"
|
|
@@ -1800,7 +1800,7 @@ async function getAuthorshipStats(repoPath = process.cwd()) {
|
|
|
1800
1800
|
// src/carbon.ts
|
|
1801
1801
|
import { homedir } from "os";
|
|
1802
1802
|
import { join } from "path";
|
|
1803
|
-
import { readFile, access } from "fs/promises";
|
|
1803
|
+
import { readFile, access, readdir } from "fs/promises";
|
|
1804
1804
|
// data/grid-factors.json
|
|
1805
1805
|
var grid_factors_default = {
|
|
1806
1806
|
vietnam: 681,
|
|
@@ -1813,10 +1813,50 @@ var grid_factors_default = {
|
|
|
1813
1813
|
// src/carbon.ts
|
|
1814
1814
|
class ClaudeLogParser {
|
|
1815
1815
|
baseDir;
|
|
1816
|
-
|
|
1816
|
+
cwd;
|
|
1817
|
+
constructor(baseDir = homedir(), cwd = process.cwd()) {
|
|
1817
1818
|
this.baseDir = baseDir;
|
|
1819
|
+
this.cwd = cwd;
|
|
1818
1820
|
}
|
|
1819
1821
|
async parse() {
|
|
1822
|
+
const slug = this.cwd.replace(/\//g, "-");
|
|
1823
|
+
const projectDir = join(this.baseDir, ".claude", "projects", slug);
|
|
1824
|
+
try {
|
|
1825
|
+
const files = (await readdir(projectDir)).filter((f2) => f2.endsWith(".jsonl"));
|
|
1826
|
+
if (files.length > 0) {
|
|
1827
|
+
let total2 = 0, output2 = 0, cache2 = 0;
|
|
1828
|
+
const sessions2 = new Set;
|
|
1829
|
+
for (const file of files) {
|
|
1830
|
+
let text3 = "";
|
|
1831
|
+
try {
|
|
1832
|
+
text3 = await readFile(join(projectDir, file), "utf-8");
|
|
1833
|
+
} catch {
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1836
|
+
for (const line of text3.split(`
|
|
1837
|
+
`)) {
|
|
1838
|
+
if (!line.trim())
|
|
1839
|
+
continue;
|
|
1840
|
+
try {
|
|
1841
|
+
const d = JSON.parse(line);
|
|
1842
|
+
const u4 = d.message && d.message.usage || d.usage;
|
|
1843
|
+
if (u4) {
|
|
1844
|
+
const inp = u4.input_tokens || 0;
|
|
1845
|
+
const out = u4.output_tokens || 0;
|
|
1846
|
+
const cr = u4.cache_read_input_tokens || 0;
|
|
1847
|
+
const cw = u4.cache_creation_input_tokens || 0;
|
|
1848
|
+
total2 += inp + out + cr + cw;
|
|
1849
|
+
output2 += out;
|
|
1850
|
+
cache2 += cr;
|
|
1851
|
+
}
|
|
1852
|
+
if (d.sessionId)
|
|
1853
|
+
sessions2.add(d.sessionId);
|
|
1854
|
+
} catch {}
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return { total: total2, output: output2, cache: cache2, sessions: sessions2.size, cost: 0 };
|
|
1858
|
+
}
|
|
1859
|
+
} catch {}
|
|
1820
1860
|
const logPath = join(this.baseDir, ".claude", "tokenomics-log.jsonl");
|
|
1821
1861
|
try {
|
|
1822
1862
|
await access(logPath);
|
|
@@ -1824,11 +1864,12 @@ class ClaudeLogParser {
|
|
|
1824
1864
|
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
1825
1865
|
}
|
|
1826
1866
|
const text2 = await readFile(logPath, "utf-8");
|
|
1827
|
-
const lines = text2.trim().split(`
|
|
1828
|
-
`).filter((l2) => l2.length > 0);
|
|
1829
1867
|
let total = 0, output = 0, cache = 0, cost = 0;
|
|
1830
1868
|
const sessions = new Set;
|
|
1831
|
-
for (const line of
|
|
1869
|
+
for (const line of text2.split(`
|
|
1870
|
+
`)) {
|
|
1871
|
+
if (!line.trim())
|
|
1872
|
+
continue;
|
|
1832
1873
|
try {
|
|
1833
1874
|
const data = JSON.parse(line);
|
|
1834
1875
|
total += data.total_tokens || 0;
|
|
@@ -1837,7 +1878,7 @@ class ClaudeLogParser {
|
|
|
1837
1878
|
cost += data.cost_usd || 0;
|
|
1838
1879
|
if (data.session_id)
|
|
1839
1880
|
sessions.add(data.session_id);
|
|
1840
|
-
} catch
|
|
1881
|
+
} catch {}
|
|
1841
1882
|
}
|
|
1842
1883
|
return { total, output, cache, sessions: sessions.size, cost };
|
|
1843
1884
|
}
|
|
@@ -2055,19 +2096,26 @@ async function main() {
|
|
|
2055
2096
|
`));
|
|
2056
2097
|
if (action === "--help" || action === "-h" || action === "help") {
|
|
2057
2098
|
console.log(import_picocolors.default.bold(`
|
|
2058
|
-
|
|
2059
|
-
console.log(
|
|
2060
|
-
console.log(
|
|
2061
|
-
|
|
2062
|
-
console.log(
|
|
2063
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2064
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2065
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2066
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2067
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2068
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2099
|
+
WHAT OUTLIER DOES`));
|
|
2100
|
+
console.log(import_picocolors.default.dim(" Reads your local git history and AI logs — on your machine — to show"));
|
|
2101
|
+
console.log(import_picocolors.default.dim(` how much of your code AI wrote, what it cost, and how to keep your skill.
|
|
2102
|
+
`));
|
|
2103
|
+
console.log(import_picocolors.default.bold("COMMANDS:"));
|
|
2104
|
+
console.log(` ${import_picocolors.default.cyan("outlier")} Run the audit (the default — same as 'status')`);
|
|
2105
|
+
console.log(` ${import_picocolors.default.cyan("outlier status")} Full audit: who wrote the code, what it cost, your limit`);
|
|
2106
|
+
console.log(` ${import_picocolors.default.cyan("outlier status --save")} Save the audit to ./outlier-audit.txt`);
|
|
2107
|
+
console.log(` ${import_picocolors.default.cyan("outlier authorship")} Just the AI-vs-human commit breakdown`);
|
|
2108
|
+
console.log(` ${import_picocolors.default.cyan("outlier carbon")} Just the token spend, cache waste & carbon`);
|
|
2109
|
+
console.log(` ${import_picocolors.default.cyan("outlier capabilities")} What tools & skills your agents can reach`);
|
|
2110
|
+
console.log(` ${import_picocolors.default.cyan("outlier policy")} Set an AI-authorship limit (local git hook / CI)`);
|
|
2111
|
+
console.log(` ${import_picocolors.default.cyan("outlier impact")} What AI reliance compounds to over time`);
|
|
2112
|
+
console.log(` ${import_picocolors.default.cyan("outlier knowledge")} The research behind the metrics`);
|
|
2113
|
+
console.log(` ${import_picocolors.default.cyan("outlier participate")} Share anonymous feedback for the deskilling study`);
|
|
2114
|
+
console.log(` ${import_picocolors.default.cyan("outlier init")} Show a once-per-day reliance greeting in new shells`);
|
|
2115
|
+
console.log(` ${import_picocolors.default.cyan("outlier uninit")} Remove that greeting`);
|
|
2069
2116
|
console.log(`
|
|
2070
|
-
` + import_picocolors.default.dim("
|
|
2117
|
+
` + import_picocolors.default.dim("Local-first: nothing ever leaves your machine."));
|
|
2118
|
+
console.log(import_picocolors.default.dim("How it works → https://github.com/rosh100yx/outlier#how-it-works"));
|
|
2071
2119
|
process.exit(0);
|
|
2072
2120
|
}
|
|
2073
2121
|
const configPath = join4(os2.homedir(), ".outlier_config");
|
|
@@ -2237,12 +2285,17 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2237
2285
|
s.stop("Audit complete");
|
|
2238
2286
|
try {
|
|
2239
2287
|
let authPct = "0%";
|
|
2288
|
+
let nmFloorStr = "";
|
|
2240
2289
|
let ruleFailures = 0;
|
|
2241
2290
|
if (gitStats) {
|
|
2242
2291
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
2292
|
+
nmFloorStr = ` ${import_picocolors.default.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
|
|
2243
2293
|
if (gitStats.ratio > 0.7)
|
|
2244
2294
|
ruleFailures++;
|
|
2245
2295
|
}
|
|
2296
|
+
const lowTrailerWarn = gitStats && gitStats.ratio < 0.1 && carbon && carbon.totalTokens > 1e6 ? `
|
|
2297
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Low %? Your agent may not tag commits — outlier counts only")}
|
|
2298
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("commits with a Co-Authored-By trailer.")}` : "";
|
|
2246
2299
|
let cachePct = "0";
|
|
2247
2300
|
let co2Str = "0.0kg";
|
|
2248
2301
|
let regionStr = "Global Average";
|
|
@@ -2288,12 +2341,13 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2288
2341
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
|
|
2289
2342
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2290
2343
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
|
|
2291
|
-
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}
|
|
2344
|
+
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}${nmFloorStr}
|
|
2292
2345
|
${import_picocolors.default.dim("│")} You ${humanBar} ${import_picocolors.default.bold(humanSov)}
|
|
2346
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Typical: solo devs 10–40% · AI-framework repos up to ~80%")}
|
|
2293
2347
|
${import_picocolors.default.dim("│")}
|
|
2294
2348
|
${import_picocolors.default.dim("│")} ${verdictZone} — ${verdictText.split(`
|
|
2295
2349
|
`).join(`
|
|
2296
|
-
` + import_picocolors.default.dim("│") + " ")}
|
|
2350
|
+
` + import_picocolors.default.dim("│") + " ")}${lowTrailerWarn}
|
|
2297
2351
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2298
2352
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
|
|
2299
2353
|
${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
|
|
@@ -2309,8 +2363,9 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2309
2363
|
${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
|
|
2310
2364
|
${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
|
|
2311
2365
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2312
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(
|
|
2313
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(
|
|
2366
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Numbers are local estimates — authorship is a proxy and")}
|
|
2367
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("carbon is rough. How it works: outlier --help")}
|
|
2368
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("Run this before you start. Keep the skill while you use the speed."))}
|
|
2314
2369
|
${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
|
|
2315
2370
|
} else {
|
|
2316
2371
|
note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
|
|
@@ -2503,9 +2558,19 @@ ${import_picocolors.default.bold("Submit here (and drop your screenshot!):")} ${
|
|
|
2503
2558
|
Read the full academic foundation at: ${import_picocolors.default.underline("https://github.com/rosh100yx/outlier")}
|
|
2504
2559
|
`);
|
|
2505
2560
|
}
|
|
2506
|
-
outro("
|
|
2561
|
+
outro("Done — nothing left your machine. (How it works: outlier --help)");
|
|
2507
2562
|
if (typeof finalReceipt !== "undefined" && finalReceipt) {
|
|
2508
2563
|
console.log(finalReceipt);
|
|
2564
|
+
if (process.argv.includes("--save")) {
|
|
2565
|
+
const stripAnsi = (s2) => s2.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2566
|
+
const savePath = join4(process.cwd(), "outlier-audit.txt");
|
|
2567
|
+
try {
|
|
2568
|
+
writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + `
|
|
2569
|
+
`);
|
|
2570
|
+
console.log(import_picocolors.default.dim(`
|
|
2571
|
+
\uD83D\uDCBE Saved to ${savePath}`));
|
|
2572
|
+
} catch {}
|
|
2573
|
+
}
|
|
2509
2574
|
}
|
|
2510
2575
|
if (action === "status") {
|
|
2511
2576
|
const agent = detectAgent();
|
|
@@ -2516,10 +2581,14 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
|
|
|
2516
2581
|
console.log(import_picocolors.default.bold(import_picocolors.default.magenta(" ↳ Ready to code? ")) + "Start your AI agent");
|
|
2517
2582
|
}
|
|
2518
2583
|
console.log("");
|
|
2519
|
-
console.log(import_picocolors.default.bold(import_picocolors.default.
|
|
2520
|
-
console.log(import_picocolors.default.bold(import_picocolors.default.
|
|
2584
|
+
console.log(import_picocolors.default.bold(import_picocolors.default.green(" \uD83D\uDCF8 Share: ")) + "Screenshot this receipt, or post your score ➔ " + import_picocolors.default.underline("https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier"));
|
|
2585
|
+
console.log(import_picocolors.default.bold(import_picocolors.default.cyan(" \uD83D\uDD2C Research: ")) + "Help the AI-deskilling study — type: " + import_picocolors.default.bold("outlier participate"));
|
|
2586
|
+
if (!process.argv.includes("--save")) {
|
|
2587
|
+
console.log(import_picocolors.default.dim(" \uD83D\uDCBE Save: outlier status --save"));
|
|
2588
|
+
}
|
|
2521
2589
|
console.log(import_picocolors.default.dim(`
|
|
2522
|
-
|
|
2590
|
+
outlier does more than this audit — see how you adopt AI, what it`));
|
|
2591
|
+
console.log(import_picocolors.default.dim(" costs, and what is actually working: ") + import_picocolors.default.bold(import_picocolors.default.cyan("outlier --help")));
|
|
2523
2592
|
}
|
|
2524
2593
|
}
|
|
2525
2594
|
main().catch(console.error);
|
package/bin/postinstall.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const cyan = (
|
|
4
|
-
const dim = (
|
|
5
|
-
const bold = (
|
|
3
|
+
const cyan = (t) => `\x1b[36m${t}\x1b[0m`;
|
|
4
|
+
const dim = (t) => `\x1b[2m${t}\x1b[0m`;
|
|
5
|
+
const bold = (t) => `\x1b[1m${t}\x1b[0m`;
|
|
6
|
+
const green = (t) => `\x1b[32m${t}\x1b[0m`;
|
|
6
7
|
|
|
7
|
-
console.log('\n' + bold('
|
|
8
|
-
console.log(dim('
|
|
9
|
-
console.log('
|
|
10
|
-
console.log(
|
|
11
|
-
console.log('
|
|
12
|
-
console.log(
|
|
13
|
-
console.log(
|
|
14
|
-
console.log(
|
|
15
|
-
console.log(
|
|
16
|
-
console.log(`
|
|
17
|
-
console.log(
|
|
18
|
-
console.log(
|
|
19
|
-
console.log(
|
|
20
|
-
console.log(
|
|
8
|
+
console.log('\n' + bold(' Outlier installed') + dim(' · AI code governance for the terminal'));
|
|
9
|
+
console.log(dim(' ──────────────────────────────────────────────────────────'));
|
|
10
|
+
console.log(' Run it before you start coding. It reads your local git history');
|
|
11
|
+
console.log(' and AI logs — ' + green('on your machine') + ' — and shows you:');
|
|
12
|
+
console.log(' • how much of your code AI wrote');
|
|
13
|
+
console.log(' • what it cost (tokens, $, wasted context, carbon)');
|
|
14
|
+
console.log(' • whether you are keeping the skill while you use the speed');
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log(' Start your first audit:');
|
|
17
|
+
console.log(` ${cyan('outlier')}`);
|
|
18
|
+
console.log('');
|
|
19
|
+
console.log(' Other commands: ' + dim('outlier --help'));
|
|
20
|
+
console.log(dim(' ──────────────────────────────────────────────────────────'));
|
|
21
|
+
console.log(' ' + green('Local-first:') + ' nothing ever leaves your machine.\n');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rosh100yx/outlier",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"description": "AI Code Governance & Capability Auditing for the Terminal. Measures AI reliance, context waste, and enforces local CI/CD policies.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"outlier": "bin/outlier.js"
|
package/src/carbon.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from 'os';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { readFile, access } from 'fs/promises';
|
|
3
|
+
import { readFile, access, readdir } from 'fs/promises';
|
|
4
4
|
import gridFactors from '../data/grid-factors.json';
|
|
5
5
|
|
|
6
6
|
export interface CarbonStats {
|
|
@@ -23,33 +23,70 @@ export interface TokenLogParser {
|
|
|
23
23
|
|
|
24
24
|
export class ClaudeLogParser implements TokenLogParser {
|
|
25
25
|
private baseDir: string;
|
|
26
|
-
|
|
26
|
+
private cwd: string;
|
|
27
|
+
constructor(baseDir = homedir(), cwd = process.cwd()) {
|
|
27
28
|
this.baseDir = baseDir;
|
|
29
|
+
this.cwd = cwd;
|
|
28
30
|
}
|
|
31
|
+
|
|
29
32
|
async parse() {
|
|
33
|
+
// Primary source: the standard Claude Code session transcripts for THIS repo.
|
|
34
|
+
// Claude Code stores them at ~/.claude/projects/<cwd-with-slashes-as-dashes>/*.jsonl,
|
|
35
|
+
// one JSON object per line; assistant turns carry `message.usage`.
|
|
36
|
+
const slug = this.cwd.replace(/\//g, '-');
|
|
37
|
+
const projectDir = join(this.baseDir, '.claude', 'projects', slug);
|
|
38
|
+
try {
|
|
39
|
+
const files = (await readdir(projectDir)).filter(f => f.endsWith('.jsonl'));
|
|
40
|
+
if (files.length > 0) {
|
|
41
|
+
let total = 0, output = 0, cache = 0;
|
|
42
|
+
const sessions = new Set<string>();
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
let text = '';
|
|
45
|
+
try { text = await readFile(join(projectDir, file), 'utf-8'); } catch { continue; }
|
|
46
|
+
for (const line of text.split('\n')) {
|
|
47
|
+
if (!line.trim()) continue;
|
|
48
|
+
try {
|
|
49
|
+
const d = JSON.parse(line);
|
|
50
|
+
const u = (d.message && d.message.usage) || d.usage;
|
|
51
|
+
if (u) {
|
|
52
|
+
const inp = u.input_tokens || 0;
|
|
53
|
+
const out = u.output_tokens || 0;
|
|
54
|
+
const cr = u.cache_read_input_tokens || 0;
|
|
55
|
+
const cw = u.cache_creation_input_tokens || 0;
|
|
56
|
+
total += inp + out + cr + cw;
|
|
57
|
+
output += out;
|
|
58
|
+
cache += cr;
|
|
59
|
+
}
|
|
60
|
+
if (d.sessionId) sessions.add(d.sessionId);
|
|
61
|
+
} catch {}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Standard transcripts carry no cost field; cost is estimated downstream.
|
|
65
|
+
return { total, output, cache, sessions: sessions.size, cost: 0 };
|
|
66
|
+
}
|
|
67
|
+
} catch {}
|
|
68
|
+
|
|
69
|
+
// Fallback: the optional tokenomics-log.jsonl (written by a custom Stop hook;
|
|
70
|
+
// carries a real cost_usd field when present).
|
|
30
71
|
const logPath = join(this.baseDir, '.claude', 'tokenomics-log.jsonl');
|
|
31
|
-
|
|
32
72
|
try {
|
|
33
73
|
await access(logPath);
|
|
34
74
|
} catch {
|
|
35
75
|
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
36
76
|
}
|
|
37
|
-
|
|
38
77
|
const text = await readFile(logPath, 'utf-8');
|
|
39
|
-
const lines = text.trim().split('\n').filter(l => l.length > 0);
|
|
40
|
-
|
|
41
78
|
let total = 0, output = 0, cache = 0, cost = 0;
|
|
42
79
|
const sessions = new Set<string>();
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
for (const line of text.split('\n')) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
45
82
|
try {
|
|
46
83
|
const data = JSON.parse(line);
|
|
47
84
|
total += data.total_tokens || 0;
|
|
48
85
|
output += data.output_tokens || 0;
|
|
49
86
|
cache += data.cache_read || 0;
|
|
50
|
-
cost += data.cost_usd || 0;
|
|
87
|
+
cost += data.cost_usd || 0;
|
|
51
88
|
if (data.session_id) sessions.add(data.session_id);
|
|
52
|
-
} catch
|
|
89
|
+
} catch {}
|
|
53
90
|
}
|
|
54
91
|
return { total, output, cache, sessions: sessions.size, cost };
|
|
55
92
|
}
|
package/src/cli.ts
CHANGED
|
@@ -84,18 +84,24 @@ async function main() {
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
if (action === '--help' || action === '-h' || action === 'help') {
|
|
87
|
-
console.log(pc.bold('\
|
|
88
|
-
console.log(
|
|
89
|
-
console.log(
|
|
90
|
-
console.log(
|
|
91
|
-
console.log(` ${pc.cyan('outlier
|
|
92
|
-
console.log(` ${pc.cyan('outlier
|
|
93
|
-
console.log(` ${pc.cyan('outlier
|
|
94
|
-
console.log(` ${pc.cyan('outlier
|
|
95
|
-
console.log(` ${pc.cyan('outlier
|
|
96
|
-
console.log(` ${pc.cyan('outlier
|
|
97
|
-
console.log(` ${pc.cyan('outlier
|
|
98
|
-
console.log(
|
|
87
|
+
console.log(pc.bold('\nWHAT OUTLIER DOES'));
|
|
88
|
+
console.log(pc.dim(' Reads your local git history and AI logs — on your machine — to show'));
|
|
89
|
+
console.log(pc.dim(' how much of your code AI wrote, what it cost, and how to keep your skill.\n'));
|
|
90
|
+
console.log(pc.bold('COMMANDS:'));
|
|
91
|
+
console.log(` ${pc.cyan('outlier')} Run the audit (the default — same as 'status')`);
|
|
92
|
+
console.log(` ${pc.cyan('outlier status')} Full audit: who wrote the code, what it cost, your limit`);
|
|
93
|
+
console.log(` ${pc.cyan('outlier status --save')} Save the audit to ./outlier-audit.txt`);
|
|
94
|
+
console.log(` ${pc.cyan('outlier authorship')} Just the AI-vs-human commit breakdown`);
|
|
95
|
+
console.log(` ${pc.cyan('outlier carbon')} Just the token spend, cache waste & carbon`);
|
|
96
|
+
console.log(` ${pc.cyan('outlier capabilities')} What tools & skills your agents can reach`);
|
|
97
|
+
console.log(` ${pc.cyan('outlier policy')} Set an AI-authorship limit (local git hook / CI)`);
|
|
98
|
+
console.log(` ${pc.cyan('outlier impact')} What AI reliance compounds to over time`);
|
|
99
|
+
console.log(` ${pc.cyan('outlier knowledge')} The research behind the metrics`);
|
|
100
|
+
console.log(` ${pc.cyan('outlier participate')} Share anonymous feedback for the deskilling study`);
|
|
101
|
+
console.log(` ${pc.cyan('outlier init')} Show a once-per-day reliance greeting in new shells`);
|
|
102
|
+
console.log(` ${pc.cyan('outlier uninit')} Remove that greeting`);
|
|
103
|
+
console.log('\n' + pc.dim('Local-first: nothing ever leaves your machine.'));
|
|
104
|
+
console.log(pc.dim('How it works → https://github.com/rosh100yx/outlier#how-it-works'));
|
|
99
105
|
process.exit(0);
|
|
100
106
|
}
|
|
101
107
|
|
|
@@ -275,13 +281,23 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
275
281
|
|
|
276
282
|
try {
|
|
277
283
|
let authPct = '0%';
|
|
284
|
+
let nmFloorStr = '';
|
|
278
285
|
let ruleFailures = 0;
|
|
279
286
|
|
|
280
287
|
if (gitStats) {
|
|
281
288
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
289
|
+
// Conservative floor: non-merge commits only (merges often lack the trailer).
|
|
290
|
+
nmFloorStr = ` ${pc.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
|
|
282
291
|
if (gitStats.ratio > 0.7) ruleFailures++;
|
|
283
292
|
}
|
|
284
293
|
|
|
294
|
+
// Honesty: a very low ratio alongside heavy token use usually means the agent
|
|
295
|
+
// doesn't tag commits, not that the human wrote everything.
|
|
296
|
+
const lowTrailerWarn =
|
|
297
|
+
gitStats && gitStats.ratio < 0.1 && carbon && carbon.totalTokens > 1_000_000
|
|
298
|
+
? `\n ${pc.dim('│')} ${pc.dim('Low %? Your agent may not tag commits — outlier counts only')}\n ${pc.dim('│')} ${pc.dim('commits with a Co-Authored-By trailer.')}`
|
|
299
|
+
: '';
|
|
300
|
+
|
|
285
301
|
let cachePct = '0';
|
|
286
302
|
let co2Str = '0.0kg';
|
|
287
303
|
let regionStr = 'Global Average';
|
|
@@ -344,10 +360,11 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
344
360
|
${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: ${repoName} · ${dateStr}`)}
|
|
345
361
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
346
362
|
${pc.dim('│')} ${pc.bold(pc.bgBlue(' WHO WROTE THE CODE '))}
|
|
347
|
-
${pc.dim('│')} AI ${aiBar} ${authorshipStr}
|
|
363
|
+
${pc.dim('│')} AI ${aiBar} ${authorshipStr}${nmFloorStr}
|
|
348
364
|
${pc.dim('│')} You ${humanBar} ${pc.bold(humanSov)}
|
|
365
|
+
${pc.dim('│')} ${pc.dim('Typical: solo devs 10–40% · AI-framework repos up to ~80%')}
|
|
349
366
|
${pc.dim('│')}
|
|
350
|
-
${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
367
|
+
${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}${lowTrailerWarn}
|
|
351
368
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
352
369
|
${pc.dim('│')} ${pc.bold(pc.bgMagenta(' WHAT IT COST '))}
|
|
353
370
|
${pc.dim('│')} Tokens used ${pc.bold(totalTokensStr)}
|
|
@@ -361,8 +378,9 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
361
378
|
${pc.dim('│')} AI cap ${pc.bold('70%')} ${pc.dim('· change with: outlier policy')}
|
|
362
379
|
${pc.dim('│')} Status ${policyStatus} ${pc.dim('·')} ${policyAction}
|
|
363
380
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
364
|
-
${pc.dim('│')} ${pc.dim(
|
|
365
|
-
${pc.dim('│')} ${pc.dim(
|
|
381
|
+
${pc.dim('│')} ${pc.dim('Numbers are local estimates — authorship is a proxy and')}
|
|
382
|
+
${pc.dim('│')} ${pc.dim('carbon is rough. How it works: outlier --help')}
|
|
383
|
+
${pc.dim('│')} ${pc.dim(pc.italic('Run this before you start. Keep the skill while you use the speed.'))}
|
|
366
384
|
${pc.dim('└────────────────────────────────────────────────────────')}`;
|
|
367
385
|
} else {
|
|
368
386
|
note(
|
|
@@ -566,10 +584,20 @@ Artifact: ${pc.cyan(reportPath)}`,
|
|
|
566
584
|
console.log(`\nRead the full academic foundation at: ${pc.underline('https://github.com/rosh100yx/outlier')}\n`);
|
|
567
585
|
}
|
|
568
586
|
|
|
569
|
-
outro('
|
|
587
|
+
outro('Done — nothing left your machine. (How it works: outlier --help)');
|
|
570
588
|
|
|
571
589
|
if (typeof finalReceipt !== 'undefined' && finalReceipt) {
|
|
572
590
|
console.log(finalReceipt);
|
|
591
|
+
|
|
592
|
+
// --save: write a plain-text (no color) copy of the receipt next to the repo.
|
|
593
|
+
if (process.argv.includes('--save')) {
|
|
594
|
+
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
595
|
+
const savePath = join(process.cwd(), 'outlier-audit.txt');
|
|
596
|
+
try {
|
|
597
|
+
writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + '\n');
|
|
598
|
+
console.log(pc.dim(`\n 💾 Saved to ${savePath}`));
|
|
599
|
+
} catch {}
|
|
600
|
+
}
|
|
573
601
|
}
|
|
574
602
|
|
|
575
603
|
if (action === 'status') {
|
|
@@ -586,13 +614,20 @@ Artifact: ${pc.cyan(reportPath)}`,
|
|
|
586
614
|
}
|
|
587
615
|
console.log('');
|
|
588
616
|
console.log(
|
|
589
|
-
pc.bold(pc.
|
|
617
|
+
pc.bold(pc.green(' 📸 Share: ')) + 'Screenshot this receipt, or post your score ➔ ' +
|
|
618
|
+
pc.underline('https://x.com/intent/tweet?text=I+just+audited+my+codebase+with+%23Outlier')
|
|
590
619
|
);
|
|
591
620
|
console.log(
|
|
592
|
-
pc.bold(pc.
|
|
621
|
+
pc.bold(pc.cyan(' 🔬 Research: ')) + 'Help the AI-deskilling study — type: ' + pc.bold('outlier participate')
|
|
622
|
+
);
|
|
623
|
+
if (!process.argv.includes('--save')) {
|
|
624
|
+
console.log(pc.dim(' 💾 Save: outlier status --save'));
|
|
625
|
+
}
|
|
626
|
+
console.log(
|
|
627
|
+
pc.dim('\n outlier does more than this audit — see how you adopt AI, what it')
|
|
593
628
|
);
|
|
594
629
|
console.log(
|
|
595
|
-
pc.dim('
|
|
630
|
+
pc.dim(' costs, and what is actually working: ') + pc.bold(pc.cyan('outlier --help'))
|
|
596
631
|
);
|
|
597
632
|
}
|
|
598
633
|
}
|