@rosh100yx/outlier 0.4.23 → 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 +140 -67
- package/bin/postinstall.js +18 -17
- package/package.json +1 -1
- package/src/carbon.ts +70 -16
- package/src/cli.ts +85 -52
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,40 +1813,86 @@ 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);
|
|
1823
1863
|
} catch {
|
|
1824
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
1864
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
1825
1865
|
}
|
|
1826
1866
|
const text2 = await readFile(logPath, "utf-8");
|
|
1827
|
-
|
|
1828
|
-
`).filter((l2) => l2.length > 0);
|
|
1829
|
-
let total = 0, output = 0, cache = 0;
|
|
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;
|
|
1835
1876
|
output += data.output_tokens || 0;
|
|
1836
1877
|
cache += data.cache_read || 0;
|
|
1878
|
+
cost += data.cost_usd || 0;
|
|
1837
1879
|
if (data.session_id)
|
|
1838
1880
|
sessions.add(data.session_id);
|
|
1839
|
-
} catch
|
|
1881
|
+
} catch {}
|
|
1840
1882
|
}
|
|
1841
|
-
return { total, output, cache, sessions: sessions.size };
|
|
1883
|
+
return { total, output, cache, sessions: sessions.size, cost };
|
|
1842
1884
|
}
|
|
1843
1885
|
}
|
|
1844
1886
|
|
|
1845
1887
|
class CursorLogParser {
|
|
1846
1888
|
async parse() {
|
|
1847
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
1889
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
1848
1890
|
}
|
|
1849
1891
|
}
|
|
1892
|
+
function estimateUsd(output, cacheRead, total) {
|
|
1893
|
+
const otherInput = Math.max(0, total - output - cacheRead);
|
|
1894
|
+
return output / 1e6 * 9 + cacheRead / 1e6 * 0.3 + otherInput / 1e6 * 3;
|
|
1895
|
+
}
|
|
1850
1896
|
function getLocalGridFactor() {
|
|
1851
1897
|
try {
|
|
1852
1898
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -1865,16 +1911,19 @@ function getLocalGridFactor() {
|
|
|
1865
1911
|
}
|
|
1866
1912
|
async function getCarbonStats() {
|
|
1867
1913
|
const parsers = [new ClaudeLogParser, new CursorLogParser];
|
|
1868
|
-
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
|
|
1914
|
+
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
|
|
1869
1915
|
for (const parser of parsers) {
|
|
1870
1916
|
const stats = await parser.parse();
|
|
1871
1917
|
totalTokens += stats.total;
|
|
1872
1918
|
outputTokens += stats.output;
|
|
1873
1919
|
cacheReadTokens += stats.cache;
|
|
1874
1920
|
sessions += stats.sessions;
|
|
1921
|
+
loggedCost += stats.cost;
|
|
1875
1922
|
}
|
|
1876
1923
|
const energyKwh = outputTokens / 1e6 * 0.662;
|
|
1877
1924
|
const localGrid = getLocalGridFactor();
|
|
1925
|
+
const costIsReal = loggedCost > 0;
|
|
1926
|
+
const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
|
|
1878
1927
|
return {
|
|
1879
1928
|
totalTokens,
|
|
1880
1929
|
outputTokens,
|
|
@@ -1884,7 +1933,9 @@ async function getCarbonStats() {
|
|
|
1884
1933
|
co2KgFrance: energyKwh * grid_factors_default.france / 1000,
|
|
1885
1934
|
localCo2Kg: energyKwh * localGrid.factor / 1000,
|
|
1886
1935
|
localRegion: localGrid.region,
|
|
1887
|
-
sessions
|
|
1936
|
+
sessions,
|
|
1937
|
+
estUsd,
|
|
1938
|
+
costIsReal
|
|
1888
1939
|
};
|
|
1889
1940
|
}
|
|
1890
1941
|
|
|
@@ -2045,19 +2096,26 @@ async function main() {
|
|
|
2045
2096
|
`));
|
|
2046
2097
|
if (action === "--help" || action === "-h" || action === "help") {
|
|
2047
2098
|
console.log(import_picocolors.default.bold(`
|
|
2048
|
-
|
|
2049
|
-
console.log(
|
|
2050
|
-
console.log(
|
|
2051
|
-
|
|
2052
|
-
console.log(
|
|
2053
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2054
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2055
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2056
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2057
|
-
console.log(` ${import_picocolors.default.cyan("outlier
|
|
2058
|
-
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`);
|
|
2059
2116
|
console.log(`
|
|
2060
|
-
` + 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"));
|
|
2061
2119
|
process.exit(0);
|
|
2062
2120
|
}
|
|
2063
2121
|
const configPath = join4(os2.homedir(), ".outlier_config");
|
|
@@ -2227,12 +2285,17 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2227
2285
|
s.stop("Audit complete");
|
|
2228
2286
|
try {
|
|
2229
2287
|
let authPct = "0%";
|
|
2288
|
+
let nmFloorStr = "";
|
|
2230
2289
|
let ruleFailures = 0;
|
|
2231
2290
|
if (gitStats) {
|
|
2232
2291
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
2292
|
+
nmFloorStr = ` ${import_picocolors.default.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
|
|
2233
2293
|
if (gitStats.ratio > 0.7)
|
|
2234
2294
|
ruleFailures++;
|
|
2235
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.")}` : "";
|
|
2236
2299
|
let cachePct = "0";
|
|
2237
2300
|
let co2Str = "0.0kg";
|
|
2238
2301
|
let regionStr = "Global Average";
|
|
@@ -2244,23 +2307,22 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2244
2307
|
regionStr = carbon.localRegion;
|
|
2245
2308
|
}
|
|
2246
2309
|
const isDanger = gitStats && gitStats.ratio > 0.7;
|
|
2247
|
-
const verdictZone = isDanger ? import_picocolors.default.red("
|
|
2248
|
-
const verdictText = isDanger ? `
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
Your human judgement remains the primary driver
|
|
2252
|
-
of logic in this system.`;
|
|
2310
|
+
const verdictZone = isDanger ? import_picocolors.default.red("Mostly AI") : import_picocolors.default.green("You're driving");
|
|
2311
|
+
const verdictText = isDanger ? `AI wrote most of this. Read it through so you can
|
|
2312
|
+
still debug it yourself when the agent isn't around.` : `You're still writing the core of this. Good —
|
|
2313
|
+
that's how you keep the skill.`;
|
|
2253
2314
|
const isInefficient = parseFloat(cachePct) > 40;
|
|
2254
|
-
const cacheVerdict = isInefficient ? import_picocolors.default.yellow("
|
|
2255
|
-
const cacheText = isInefficient ? `
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("
|
|
2259
|
-
const policyAction = ruleFailures > 0 ? "
|
|
2315
|
+
const cacheVerdict = isInefficient ? import_picocolors.default.yellow("Lots of re-reads") : import_picocolors.default.green("Lean");
|
|
2316
|
+
const cacheText = isInefficient ? `Most of your tokens just re-send old context.
|
|
2317
|
+
It's normal for agents, but it's most of the bill.` : `Little wasted context. Your spend is mostly
|
|
2318
|
+
real work.`;
|
|
2319
|
+
const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("Over your limit") : import_picocolors.default.green("Within limit");
|
|
2320
|
+
const policyAction = ruleFailures > 0 ? "Heads up only — nothing was blocked." : "Nothing to do.";
|
|
2260
2321
|
const fmtTokens = (n2) => n2 >= 1e9 ? (n2 / 1e9).toFixed(1) + "B" : n2 >= 1e6 ? (n2 / 1e6).toFixed(1) + "M" : n2 >= 1000 ? (n2 / 1000).toFixed(1) + "k" : String(n2);
|
|
2261
2322
|
const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : "0";
|
|
2262
|
-
const
|
|
2263
|
-
const
|
|
2323
|
+
const estUsdStr = carbon ? "$" + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? "" : import_picocolors.default.dim(" (rough)")) : import_picocolors.default.dim("n/a");
|
|
2324
|
+
const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + "%" : "100%";
|
|
2325
|
+
const authorshipStr = import_picocolors.default.bold(authPct) + (isDanger ? import_picocolors.default.red(" AI-written") : import_picocolors.default.green(" AI-written"));
|
|
2264
2326
|
const getProgressBar = (pct, length = 10) => {
|
|
2265
2327
|
const filled = Math.max(0, Math.min(length, Math.round(pct / 100 * length)));
|
|
2266
2328
|
return "▰".repeat(filled) + "▱".repeat(length - filled);
|
|
@@ -2275,38 +2337,35 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2275
2337
|
const repoName = process.cwd().split("/").pop() || "Unknown";
|
|
2276
2338
|
finalReceipt = `
|
|
2277
2339
|
${import_picocolors.default.dim("┌────────────────────────────────────────────────────────")}
|
|
2278
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold("::
|
|
2279
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`::
|
|
2340
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold(":: CODE AUDIT")}
|
|
2341
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
|
|
2280
2342
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2281
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue("
|
|
2282
|
-
${import_picocolors.default.dim("│")} AI
|
|
2283
|
-
${import_picocolors.default.dim("│")}
|
|
2343
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
|
|
2344
|
+
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}${nmFloorStr}
|
|
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%")}
|
|
2284
2347
|
${import_picocolors.default.dim("│")}
|
|
2285
|
-
${import_picocolors.default.dim("│")}
|
|
2286
|
-
${import_picocolors.default.dim("│")} ${verdictText.split(`
|
|
2348
|
+
${import_picocolors.default.dim("│")} ${verdictZone} — ${verdictText.split(`
|
|
2287
2349
|
`).join(`
|
|
2288
|
-
` + import_picocolors.default.dim("│") + "
|
|
2350
|
+
` + import_picocolors.default.dim("│") + " ")}${lowTrailerWarn}
|
|
2289
2351
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2290
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta("
|
|
2291
|
-
${import_picocolors.default.dim("│")} Tokens
|
|
2292
|
-
${import_picocolors.default.dim("│")}
|
|
2293
|
-
${import_picocolors.default.dim("│")}
|
|
2352
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
|
|
2353
|
+
${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
|
|
2354
|
+
${import_picocolors.default.dim("│")} Est. spend ${import_picocolors.default.bold(estUsdStr)}
|
|
2355
|
+
${import_picocolors.default.dim("│")} Re-used context ${cacheBar} ${import_picocolors.default.bold(cachePct + "%")}
|
|
2356
|
+
${import_picocolors.default.dim("│")} Energy ${import_picocolors.default.bold(co2Str)} ${import_picocolors.default.dim(`(${regionStr} grid, rough)`)}
|
|
2294
2357
|
${import_picocolors.default.dim("│")}
|
|
2295
|
-
${import_picocolors.default.dim("│")}
|
|
2296
|
-
${import_picocolors.default.dim("│")} ${cacheText.split(`
|
|
2358
|
+
${import_picocolors.default.dim("│")} ${cacheVerdict} — ${cacheText.split(`
|
|
2297
2359
|
`).join(`
|
|
2298
|
-
` + import_picocolors.default.dim("│") + "
|
|
2360
|
+
` + import_picocolors.default.dim("│") + " ")}
|
|
2299
2361
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2300
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black("
|
|
2301
|
-
${import_picocolors.default.dim("│")}
|
|
2302
|
-
${import_picocolors.default.dim("│")}
|
|
2362
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" YOUR LIMIT ")))}
|
|
2363
|
+
${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
|
|
2364
|
+
${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
|
|
2303
2365
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2304
|
-
${import_picocolors.default.dim("│")}
|
|
2305
|
-
${import_picocolors.default.dim("│")}
|
|
2306
|
-
${import_picocolors.default.dim("│")}
|
|
2307
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("human mastery is the only true moat."))}
|
|
2308
|
-
${import_picocolors.default.dim("│")}
|
|
2309
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.cyan("***STAY VIGILANT***"))}
|
|
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."))}
|
|
2310
2369
|
${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
|
|
2311
2370
|
} else {
|
|
2312
2371
|
note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
|
|
@@ -2499,9 +2558,19 @@ ${import_picocolors.default.bold("Submit here (and drop your screenshot!):")} ${
|
|
|
2499
2558
|
Read the full academic foundation at: ${import_picocolors.default.underline("https://github.com/rosh100yx/outlier")}
|
|
2500
2559
|
`);
|
|
2501
2560
|
}
|
|
2502
|
-
outro("
|
|
2561
|
+
outro("Done — nothing left your machine. (How it works: outlier --help)");
|
|
2503
2562
|
if (typeof finalReceipt !== "undefined" && finalReceipt) {
|
|
2504
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
|
+
}
|
|
2505
2574
|
}
|
|
2506
2575
|
if (action === "status") {
|
|
2507
2576
|
const agent = detectAgent();
|
|
@@ -2512,10 +2581,14 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
|
|
|
2512
2581
|
console.log(import_picocolors.default.bold(import_picocolors.default.magenta(" ↳ Ready to code? ")) + "Start your AI agent");
|
|
2513
2582
|
}
|
|
2514
2583
|
console.log("");
|
|
2515
|
-
console.log(import_picocolors.default.bold(import_picocolors.default.
|
|
2516
|
-
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
|
+
}
|
|
2517
2589
|
console.log(import_picocolors.default.dim(`
|
|
2518
|
-
|
|
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")));
|
|
2519
2592
|
}
|
|
2520
2593
|
}
|
|
2521
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 {
|
|
@@ -13,51 +13,98 @@ export interface CarbonStats {
|
|
|
13
13
|
localCo2Kg: number;
|
|
14
14
|
localRegion: string;
|
|
15
15
|
sessions: number;
|
|
16
|
+
estUsd: number; // estimated spend in USD
|
|
17
|
+
costIsReal: boolean; // true if summed from the log's own cost field, false if estimated from tokens
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface TokenLogParser {
|
|
19
|
-
parse(): Promise<{ total: number, output: number, cache: number, sessions: number }>;
|
|
21
|
+
parse(): Promise<{ total: number, output: number, cache: number, sessions: number, cost: number }>;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export class ClaudeLogParser implements TokenLogParser {
|
|
23
25
|
private baseDir: string;
|
|
24
|
-
|
|
26
|
+
private cwd: string;
|
|
27
|
+
constructor(baseDir = homedir(), cwd = process.cwd()) {
|
|
25
28
|
this.baseDir = baseDir;
|
|
29
|
+
this.cwd = cwd;
|
|
26
30
|
}
|
|
31
|
+
|
|
27
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).
|
|
28
71
|
const logPath = join(this.baseDir, '.claude', 'tokenomics-log.jsonl');
|
|
29
|
-
|
|
30
72
|
try {
|
|
31
73
|
await access(logPath);
|
|
32
74
|
} catch {
|
|
33
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
75
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
34
76
|
}
|
|
35
|
-
|
|
36
77
|
const text = await readFile(logPath, 'utf-8');
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let total = 0, output = 0, cache = 0;
|
|
78
|
+
let total = 0, output = 0, cache = 0, cost = 0;
|
|
40
79
|
const sessions = new Set<string>();
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
for (const line of text.split('\n')) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
43
82
|
try {
|
|
44
83
|
const data = JSON.parse(line);
|
|
45
84
|
total += data.total_tokens || 0;
|
|
46
85
|
output += data.output_tokens || 0;
|
|
47
86
|
cache += data.cache_read || 0;
|
|
87
|
+
cost += data.cost_usd || 0;
|
|
48
88
|
if (data.session_id) sessions.add(data.session_id);
|
|
49
|
-
} catch
|
|
89
|
+
} catch {}
|
|
50
90
|
}
|
|
51
|
-
return { total, output, cache, sessions: sessions.size };
|
|
91
|
+
return { total, output, cache, sessions: sessions.size, cost };
|
|
52
92
|
}
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
class CursorLogParser implements TokenLogParser {
|
|
56
96
|
async parse() {
|
|
57
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
97
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
58
98
|
}
|
|
59
99
|
}
|
|
60
100
|
|
|
101
|
+
// Rough blended token pricing (USD per 1M tokens) for when the log has no cost field.
|
|
102
|
+
// Conservative mid-points across current models; labeled "rough" in the UI.
|
|
103
|
+
function estimateUsd(output: number, cacheRead: number, total: number): number {
|
|
104
|
+
const otherInput = Math.max(0, total - output - cacheRead);
|
|
105
|
+
return (output / 1e6) * 9 + (cacheRead / 1e6) * 0.3 + (otherInput / 1e6) * 3;
|
|
106
|
+
}
|
|
107
|
+
|
|
61
108
|
function getLocalGridFactor(): { region: string, factor: number } {
|
|
62
109
|
try {
|
|
63
110
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -73,7 +120,7 @@ function getLocalGridFactor(): { region: string, factor: number } {
|
|
|
73
120
|
export async function getCarbonStats(): Promise<CarbonStats> {
|
|
74
121
|
const parsers: TokenLogParser[] = [new ClaudeLogParser(), new CursorLogParser()];
|
|
75
122
|
|
|
76
|
-
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
|
|
123
|
+
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
|
|
77
124
|
|
|
78
125
|
for (const parser of parsers) {
|
|
79
126
|
const stats = await parser.parse();
|
|
@@ -81,11 +128,16 @@ export async function getCarbonStats(): Promise<CarbonStats> {
|
|
|
81
128
|
outputTokens += stats.output;
|
|
82
129
|
cacheReadTokens += stats.cache;
|
|
83
130
|
sessions += stats.sessions;
|
|
131
|
+
loggedCost += stats.cost;
|
|
84
132
|
}
|
|
85
133
|
|
|
86
134
|
const energyKwh = (outputTokens / 1_000_000) * 0.662;
|
|
87
135
|
const localGrid = getLocalGridFactor();
|
|
88
136
|
|
|
137
|
+
// Prefer the log's own cost field (accurate); fall back to a rough token estimate.
|
|
138
|
+
const costIsReal = loggedCost > 0;
|
|
139
|
+
const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
|
|
140
|
+
|
|
89
141
|
return {
|
|
90
142
|
totalTokens,
|
|
91
143
|
outputTokens,
|
|
@@ -95,6 +147,8 @@ export async function getCarbonStats(): Promise<CarbonStats> {
|
|
|
95
147
|
co2KgFrance: (energyKwh * gridFactors.france) / 1000,
|
|
96
148
|
localCo2Kg: (energyKwh * localGrid.factor) / 1000,
|
|
97
149
|
localRegion: localGrid.region,
|
|
98
|
-
sessions
|
|
150
|
+
sessions,
|
|
151
|
+
estUsd,
|
|
152
|
+
costIsReal
|
|
99
153
|
};
|
|
100
154
|
}
|
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';
|
|
@@ -297,19 +313,19 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
297
313
|
// (The old @clack dashboard panel was removed: it duplicated the receipt's
|
|
298
314
|
// numbers in a second format, doubling the output on every run.)
|
|
299
315
|
const isDanger = gitStats && gitStats.ratio > 0.7;
|
|
300
|
-
const verdictZone = isDanger ? pc.red('
|
|
301
|
-
const verdictText = isDanger
|
|
302
|
-
? `
|
|
303
|
-
: `You
|
|
304
|
-
|
|
316
|
+
const verdictZone = isDanger ? pc.red('Mostly AI') : pc.green('You\'re driving');
|
|
317
|
+
const verdictText = isDanger
|
|
318
|
+
? `AI wrote most of this. Read it through so you can\n still debug it yourself when the agent isn't around.`
|
|
319
|
+
: `You're still writing the core of this. Good —\n that's how you keep the skill.`;
|
|
320
|
+
|
|
305
321
|
const isInefficient = parseFloat(cachePct) > 40;
|
|
306
|
-
const cacheVerdict = isInefficient ? pc.yellow('
|
|
307
|
-
const cacheText = isInefficient
|
|
308
|
-
? `
|
|
309
|
-
: `
|
|
310
|
-
|
|
311
|
-
const policyStatus = ruleFailures > 0 ? pc.red('
|
|
312
|
-
const policyAction = ruleFailures > 0 ? '
|
|
322
|
+
const cacheVerdict = isInefficient ? pc.yellow('Lots of re-reads') : pc.green('Lean');
|
|
323
|
+
const cacheText = isInefficient
|
|
324
|
+
? `Most of your tokens just re-send old context.\n It's normal for agents, but it's most of the bill.`
|
|
325
|
+
: `Little wasted context. Your spend is mostly\n real work.`;
|
|
326
|
+
|
|
327
|
+
const policyStatus = ruleFailures > 0 ? pc.red('Over your limit') : pc.green('Within limit');
|
|
328
|
+
const policyAction = ruleFailures > 0 ? 'Heads up only — nothing was blocked.' : 'Nothing to do.';
|
|
313
329
|
|
|
314
330
|
const fmtTokens = (n: number) =>
|
|
315
331
|
n >= 1_000_000_000 ? (n / 1_000_000_000).toFixed(1) + 'B'
|
|
@@ -317,8 +333,11 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
317
333
|
: n >= 1_000 ? (n / 1_000).toFixed(1) + 'k'
|
|
318
334
|
: String(n);
|
|
319
335
|
const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : '0';
|
|
320
|
-
const
|
|
321
|
-
|
|
336
|
+
const estUsdStr = carbon
|
|
337
|
+
? '$' + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? '' : pc.dim(' (rough)'))
|
|
338
|
+
: pc.dim('n/a');
|
|
339
|
+
const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + '%' : '100%';
|
|
340
|
+
const authorshipStr = pc.bold(authPct) + (isDanger ? pc.red(' AI-written') : pc.green(' AI-written'));
|
|
322
341
|
|
|
323
342
|
const getProgressBar = (pct: number, length = 10) => {
|
|
324
343
|
const filled = Math.max(0, Math.min(length, Math.round((pct / 100) * length)));
|
|
@@ -337,34 +356,31 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
337
356
|
|
|
338
357
|
finalReceipt = `
|
|
339
358
|
${pc.dim('┌────────────────────────────────────────────────────────')}
|
|
340
|
-
${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold('::
|
|
341
|
-
${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`::
|
|
359
|
+
${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold(':: CODE AUDIT')}
|
|
360
|
+
${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: ${repoName} · ${dateStr}`)}
|
|
342
361
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
343
|
-
${pc.dim('│')} ${pc.bold(pc.bgBlue('
|
|
344
|
-
${pc.dim('│')} AI
|
|
345
|
-
${pc.dim('│')}
|
|
362
|
+
${pc.dim('│')} ${pc.bold(pc.bgBlue(' WHO WROTE THE CODE '))}
|
|
363
|
+
${pc.dim('│')} AI ${aiBar} ${authorshipStr}${nmFloorStr}
|
|
364
|
+
${pc.dim('│')} You ${humanBar} ${pc.bold(humanSov)}
|
|
365
|
+
${pc.dim('│')} ${pc.dim('Typical: solo devs 10–40% · AI-framework repos up to ~80%')}
|
|
346
366
|
${pc.dim('│')}
|
|
347
|
-
${pc.dim('│')}
|
|
348
|
-
${pc.dim('│')} ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
367
|
+
${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}${lowTrailerWarn}
|
|
349
368
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
350
|
-
${pc.dim('│')} ${pc.bold(pc.bgMagenta('
|
|
351
|
-
${pc.dim('│')} Tokens
|
|
352
|
-
${pc.dim('│')}
|
|
353
|
-
${pc.dim('│')}
|
|
369
|
+
${pc.dim('│')} ${pc.bold(pc.bgMagenta(' WHAT IT COST '))}
|
|
370
|
+
${pc.dim('│')} Tokens used ${pc.bold(totalTokensStr)}
|
|
371
|
+
${pc.dim('│')} Est. spend ${pc.bold(estUsdStr)}
|
|
372
|
+
${pc.dim('│')} Re-used context ${cacheBar} ${pc.bold(cachePct + '%')}
|
|
373
|
+
${pc.dim('│')} Energy ${pc.bold(co2Str)} ${pc.dim(`(${regionStr} grid, rough)`)}
|
|
354
374
|
${pc.dim('│')}
|
|
355
|
-
${pc.dim('│')}
|
|
356
|
-
${pc.dim('│')} ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
375
|
+
${pc.dim('│')} ${cacheVerdict} — ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
357
376
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
358
|
-
${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black('
|
|
359
|
-
${pc.dim('│')}
|
|
360
|
-
${pc.dim('│')}
|
|
377
|
+
${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black(' YOUR LIMIT ')))}
|
|
378
|
+
${pc.dim('│')} AI cap ${pc.bold('70%')} ${pc.dim('· change with: outlier policy')}
|
|
379
|
+
${pc.dim('│')} Status ${policyStatus} ${pc.dim('·')} ${policyAction}
|
|
361
380
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
362
|
-
${pc.dim('│')}
|
|
363
|
-
${pc.dim('│')}
|
|
364
|
-
${pc.dim('│')}
|
|
365
|
-
${pc.dim('│')} ${pc.italic(pc.dim('human mastery is the only true moat.'))}
|
|
366
|
-
${pc.dim('│')}
|
|
367
|
-
${pc.dim('│')} ${pc.bold(pc.cyan('***STAY VIGILANT***'))}
|
|
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.'))}
|
|
368
384
|
${pc.dim('└────────────────────────────────────────────────────────')}`;
|
|
369
385
|
} else {
|
|
370
386
|
note(
|
|
@@ -568,10 +584,20 @@ Artifact: ${pc.cyan(reportPath)}`,
|
|
|
568
584
|
console.log(`\nRead the full academic foundation at: ${pc.underline('https://github.com/rosh100yx/outlier')}\n`);
|
|
569
585
|
}
|
|
570
586
|
|
|
571
|
-
outro('
|
|
587
|
+
outro('Done — nothing left your machine. (How it works: outlier --help)');
|
|
572
588
|
|
|
573
589
|
if (typeof finalReceipt !== 'undefined' && finalReceipt) {
|
|
574
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
|
+
}
|
|
575
601
|
}
|
|
576
602
|
|
|
577
603
|
if (action === 'status') {
|
|
@@ -588,13 +614,20 @@ Artifact: ${pc.cyan(reportPath)}`,
|
|
|
588
614
|
}
|
|
589
615
|
console.log('');
|
|
590
616
|
console.log(
|
|
591
|
-
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')
|
|
592
619
|
);
|
|
593
620
|
console.log(
|
|
594
|
-
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')
|
|
595
628
|
);
|
|
596
629
|
console.log(
|
|
597
|
-
pc.dim('
|
|
630
|
+
pc.dim(' costs, and what is actually working: ') + pc.bold(pc.cyan('outlier --help'))
|
|
598
631
|
);
|
|
599
632
|
}
|
|
600
633
|
}
|