@rosh100yx/outlier 0.4.22 → 0.4.24
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/bin/outlier.js +52 -84
- package/package.json +1 -1
- package/src/carbon.ts +25 -8
- package/src/cli.ts +47 -80
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.24",
|
|
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"
|
|
@@ -1821,12 +1821,12 @@ class ClaudeLogParser {
|
|
|
1821
1821
|
try {
|
|
1822
1822
|
await access(logPath);
|
|
1823
1823
|
} catch {
|
|
1824
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
1824
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
1825
1825
|
}
|
|
1826
1826
|
const text2 = await readFile(logPath, "utf-8");
|
|
1827
1827
|
const lines = text2.trim().split(`
|
|
1828
1828
|
`).filter((l2) => l2.length > 0);
|
|
1829
|
-
let total = 0, output = 0, cache = 0;
|
|
1829
|
+
let total = 0, output = 0, cache = 0, cost = 0;
|
|
1830
1830
|
const sessions = new Set;
|
|
1831
1831
|
for (const line of lines) {
|
|
1832
1832
|
try {
|
|
@@ -1834,19 +1834,24 @@ class ClaudeLogParser {
|
|
|
1834
1834
|
total += data.total_tokens || 0;
|
|
1835
1835
|
output += data.output_tokens || 0;
|
|
1836
1836
|
cache += data.cache_read || 0;
|
|
1837
|
+
cost += data.cost_usd || 0;
|
|
1837
1838
|
if (data.session_id)
|
|
1838
1839
|
sessions.add(data.session_id);
|
|
1839
1840
|
} catch (e) {}
|
|
1840
1841
|
}
|
|
1841
|
-
return { total, output, cache, sessions: sessions.size };
|
|
1842
|
+
return { total, output, cache, sessions: sessions.size, cost };
|
|
1842
1843
|
}
|
|
1843
1844
|
}
|
|
1844
1845
|
|
|
1845
1846
|
class CursorLogParser {
|
|
1846
1847
|
async parse() {
|
|
1847
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
1848
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
1848
1849
|
}
|
|
1849
1850
|
}
|
|
1851
|
+
function estimateUsd(output, cacheRead, total) {
|
|
1852
|
+
const otherInput = Math.max(0, total - output - cacheRead);
|
|
1853
|
+
return output / 1e6 * 9 + cacheRead / 1e6 * 0.3 + otherInput / 1e6 * 3;
|
|
1854
|
+
}
|
|
1850
1855
|
function getLocalGridFactor() {
|
|
1851
1856
|
try {
|
|
1852
1857
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -1865,16 +1870,19 @@ function getLocalGridFactor() {
|
|
|
1865
1870
|
}
|
|
1866
1871
|
async function getCarbonStats() {
|
|
1867
1872
|
const parsers = [new ClaudeLogParser, new CursorLogParser];
|
|
1868
|
-
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
|
|
1873
|
+
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
|
|
1869
1874
|
for (const parser of parsers) {
|
|
1870
1875
|
const stats = await parser.parse();
|
|
1871
1876
|
totalTokens += stats.total;
|
|
1872
1877
|
outputTokens += stats.output;
|
|
1873
1878
|
cacheReadTokens += stats.cache;
|
|
1874
1879
|
sessions += stats.sessions;
|
|
1880
|
+
loggedCost += stats.cost;
|
|
1875
1881
|
}
|
|
1876
1882
|
const energyKwh = outputTokens / 1e6 * 0.662;
|
|
1877
1883
|
const localGrid = getLocalGridFactor();
|
|
1884
|
+
const costIsReal = loggedCost > 0;
|
|
1885
|
+
const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
|
|
1878
1886
|
return {
|
|
1879
1887
|
totalTokens,
|
|
1880
1888
|
outputTokens,
|
|
@@ -1884,7 +1892,9 @@ async function getCarbonStats() {
|
|
|
1884
1892
|
co2KgFrance: energyKwh * grid_factors_default.france / 1000,
|
|
1885
1893
|
localCo2Kg: energyKwh * localGrid.factor / 1000,
|
|
1886
1894
|
localRegion: localGrid.region,
|
|
1887
|
-
sessions
|
|
1895
|
+
sessions,
|
|
1896
|
+
estUsd,
|
|
1897
|
+
costIsReal
|
|
1888
1898
|
};
|
|
1889
1899
|
}
|
|
1890
1900
|
|
|
@@ -1995,7 +2005,6 @@ var ASCII_LOGO = `
|
|
|
1995
2005
|
`;
|
|
1996
2006
|
var finalReceipt = "";
|
|
1997
2007
|
async function runOnboarding() {
|
|
1998
|
-
console.clear();
|
|
1999
2008
|
console.log(import_picocolors.default.cyan(ASCII_LOGO));
|
|
2000
2009
|
intro(import_picocolors.default.inverse(" outlier: Welcome "));
|
|
2001
2010
|
note(`Outlier is a local-first Policy Engine & Governance Framework for AI Engineering.
|
|
@@ -2040,7 +2049,6 @@ async function main() {
|
|
|
2040
2049
|
process.exit(0);
|
|
2041
2050
|
action = "status";
|
|
2042
2051
|
}
|
|
2043
|
-
console.clear();
|
|
2044
2052
|
console.log(import_picocolors.default.cyan(ASCII_LOGO));
|
|
2045
2053
|
const pkg = require_package();
|
|
2046
2054
|
console.log(import_picocolors.default.dim(` Outlier v${pkg.version} · AI Code Reliance & Telemetry Engine
|
|
@@ -2230,29 +2238,10 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2230
2238
|
try {
|
|
2231
2239
|
let authPct = "0%";
|
|
2232
2240
|
let ruleFailures = 0;
|
|
2233
|
-
let authWarning = "";
|
|
2234
|
-
let wittyRemark = isStrict ? "" : "No git history (・_・ヾ";
|
|
2235
|
-
let mentorString = "";
|
|
2236
2241
|
if (gitStats) {
|
|
2237
2242
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
2238
|
-
if (
|
|
2239
|
-
if (gitStats.ratio < 0.1)
|
|
2240
|
-
wittyRemark = "Artisan, hand-crafted code. Very 2019 of you (=^ ◡ ^=)";
|
|
2241
|
-
else if (gitStats.ratio < 0.6)
|
|
2242
|
-
wittyRemark = "A true centaur. Half human, half matrix (=`ω´=)";
|
|
2243
|
-
else if (gitStats.ratio < 0.95)
|
|
2244
|
-
wittyRemark = "Orchestrating the swarm. You are the manager now (ФДФ)";
|
|
2245
|
-
else
|
|
2246
|
-
wittyRemark = "100% Cybernetic. Codebase goes brrrrr (=ಠᆽಠ=)";
|
|
2247
|
-
}
|
|
2248
|
-
if (gitStats.ratio > 0.7) {
|
|
2249
|
-
authWarning = import_picocolors.default.red(isStrict ? `⚠ High Risk Surface: ${authPct} AI-generated. Human review required.` : `⚠ Mentoring Emergency: ${authPct} AI-generated. High risk of skill atrophy.`);
|
|
2250
|
-
if (!isStrict) {
|
|
2251
|
-
mentorString = `
|
|
2252
|
-
mentor: ${import_picocolors.default.blue("\uD83D\uDCA1 Architecture Challenge Pending (See Git Hook)")}`;
|
|
2253
|
-
}
|
|
2243
|
+
if (gitStats.ratio > 0.7)
|
|
2254
2244
|
ruleFailures++;
|
|
2255
|
-
}
|
|
2256
2245
|
}
|
|
2257
2246
|
let cachePct = "0";
|
|
2258
2247
|
let co2Str = "0.0kg";
|
|
@@ -2264,39 +2253,23 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2264
2253
|
co2Str = `${carbon.localCo2Kg.toFixed(2)}kg CO2`;
|
|
2265
2254
|
regionStr = carbon.localRegion;
|
|
2266
2255
|
}
|
|
2267
|
-
const vibeRow = !isStrict ? `
|
|
2268
|
-
vibe: ${import_picocolors.default.italic(wittyRemark)}` : "";
|
|
2269
|
-
const capIcon = isStrict ? "" : "(Ф∇Ф) ";
|
|
2270
|
-
const authIcon = isStrict ? "" : "(=^・ω・^=) ";
|
|
2271
|
-
const costIcon = isStrict ? "" : "(O_O;) ";
|
|
2272
|
-
const failIcon = isStrict ? "⚠" : "(=ಠᆽಠ=)";
|
|
2273
|
-
const passIcon = isStrict ? "✓" : "(=^ ◡ ^=)";
|
|
2274
|
-
note(`${capIcon}${import_picocolors.default.dim("[1] Capability Engine")} ${import_picocolors.default.cyan("▰▰▰▰▰▰▱▱▱▱")} ${import_picocolors.default.bold("Active")}
|
|
2275
|
-
status: ${import_picocolors.default.green("✓ Configured")}
|
|
2276
|
-
${authIcon}${import_picocolors.default.dim("[2] AI Code Reliance")} ${import_picocolors.default.yellow("▰▰▰▰▰▰▰▰▱▱")} ${import_picocolors.default.bold(`${authPct} Reliance`)}${vibeRow}
|
|
2277
|
-
gate: ${gitStats && gitStats.ratio <= 0.7 ? import_picocolors.default.green("✓ Human Mastery Sustained") : `${import_picocolors.default.red(`${failIcon} Deskilling Risk Detected`)} ${import_picocolors.default.red("⚠ Security Audit Required")}`}${mentorString}
|
|
2278
|
-
${costIcon}${import_picocolors.default.dim("[3] Tokenomics & Cost")} ${import_picocolors.default.magenta("▰▰▰▰▰▰▰▰▰▱")} ${import_picocolors.default.bold(`${cachePct}% Cache Bloat`)}
|
|
2279
|
-
waste: ${import_picocolors.default.yellow(`⚠ ${cachePct}% of tokens are redundant context reads`)}
|
|
2280
|
-
carbon: ${import_picocolors.default.green(`✓ ${co2Str} (Est. ${regionStr} Grid)`)}
|
|
2281
|
-
${import_picocolors.default.bold("Governance:")} ${ruleFailures > 0 ? import_picocolors.default.red(`${failIcon} ${ruleFailures + 1} policy failures`) : import_picocolors.default.green(`${passIcon} All clear`)}`, `${import_picocolors.default.bold("[outlier]")} ${5 - (ruleFailures + 1)}/5 policies • ${authWarning || import_picocolors.default.green(`${passIcon} safe surface`)} • ${co2Str}`);
|
|
2282
|
-
const timestamp = new Date().toISOString().split("T")[0];
|
|
2283
2256
|
const isDanger = gitStats && gitStats.ratio > 0.7;
|
|
2284
|
-
const verdictZone = isDanger ? import_picocolors.default.red("
|
|
2285
|
-
const verdictText = isDanger ? `
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
Your human judgement remains the primary driver
|
|
2289
|
-
of logic in this system.`;
|
|
2257
|
+
const verdictZone = isDanger ? import_picocolors.default.red("Mostly AI") : import_picocolors.default.green("You're driving");
|
|
2258
|
+
const verdictText = isDanger ? `AI wrote most of this. Read it through so you can
|
|
2259
|
+
still debug it yourself when the agent isn't around.` : `You're still writing the core of this. Good —
|
|
2260
|
+
that's how you keep the skill.`;
|
|
2290
2261
|
const isInefficient = parseFloat(cachePct) > 40;
|
|
2291
|
-
const cacheVerdict = isInefficient ? import_picocolors.default.yellow("
|
|
2292
|
-
const cacheText = isInefficient ? `
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("
|
|
2296
|
-
const policyAction = ruleFailures > 0 ? "
|
|
2297
|
-
const
|
|
2298
|
-
const
|
|
2299
|
-
const
|
|
2262
|
+
const cacheVerdict = isInefficient ? import_picocolors.default.yellow("Lots of re-reads") : import_picocolors.default.green("Lean");
|
|
2263
|
+
const cacheText = isInefficient ? `Most of your tokens just re-send old context.
|
|
2264
|
+
It's normal for agents, but it's most of the bill.` : `Little wasted context. Your spend is mostly
|
|
2265
|
+
real work.`;
|
|
2266
|
+
const policyStatus = ruleFailures > 0 ? import_picocolors.default.red("Over your limit") : import_picocolors.default.green("Within limit");
|
|
2267
|
+
const policyAction = ruleFailures > 0 ? "Heads up only — nothing was blocked." : "Nothing to do.";
|
|
2268
|
+
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);
|
|
2269
|
+
const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : "0";
|
|
2270
|
+
const estUsdStr = carbon ? "$" + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? "" : import_picocolors.default.dim(" (rough)")) : import_picocolors.default.dim("n/a");
|
|
2271
|
+
const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + "%" : "100%";
|
|
2272
|
+
const authorshipStr = import_picocolors.default.bold(authPct) + (isDanger ? import_picocolors.default.red(" AI-written") : import_picocolors.default.green(" AI-written"));
|
|
2300
2273
|
const getProgressBar = (pct, length = 10) => {
|
|
2301
2274
|
const filled = Math.max(0, Math.min(length, Math.round(pct / 100 * length)));
|
|
2302
2275
|
return "▰".repeat(filled) + "▱".repeat(length - filled);
|
|
@@ -2311,38 +2284,33 @@ ${import_picocolors.default.bold("Governance:")} ${ruleFailures > 0 ? import_pic
|
|
|
2311
2284
|
const repoName = process.cwd().split("/").pop() || "Unknown";
|
|
2312
2285
|
finalReceipt = `
|
|
2313
2286
|
${import_picocolors.default.dim("┌────────────────────────────────────────────────────────")}
|
|
2314
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold("::
|
|
2315
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`::
|
|
2287
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█")} ${import_picocolors.default.bold(":: CODE AUDIT")}
|
|
2288
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
|
|
2316
2289
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2317
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue("
|
|
2318
|
-
${import_picocolors.default.dim("│")} AI
|
|
2319
|
-
${import_picocolors.default.dim("│")}
|
|
2290
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
|
|
2291
|
+
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}
|
|
2292
|
+
${import_picocolors.default.dim("│")} You ${humanBar} ${import_picocolors.default.bold(humanSov)}
|
|
2320
2293
|
${import_picocolors.default.dim("│")}
|
|
2321
|
-
${import_picocolors.default.dim("│")}
|
|
2322
|
-
${import_picocolors.default.dim("│")} ${verdictText.split(`
|
|
2294
|
+
${import_picocolors.default.dim("│")} ${verdictZone} — ${verdictText.split(`
|
|
2323
2295
|
`).join(`
|
|
2324
|
-
` + import_picocolors.default.dim("│") + "
|
|
2296
|
+
` + import_picocolors.default.dim("│") + " ")}
|
|
2325
2297
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2326
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta("
|
|
2327
|
-
${import_picocolors.default.dim("│")} Tokens
|
|
2328
|
-
${import_picocolors.default.dim("│")}
|
|
2329
|
-
${import_picocolors.default.dim("│")}
|
|
2298
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
|
|
2299
|
+
${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
|
|
2300
|
+
${import_picocolors.default.dim("│")} Est. spend ${import_picocolors.default.bold(estUsdStr)}
|
|
2301
|
+
${import_picocolors.default.dim("│")} Re-used context ${cacheBar} ${import_picocolors.default.bold(cachePct + "%")}
|
|
2302
|
+
${import_picocolors.default.dim("│")} Energy ${import_picocolors.default.bold(co2Str)} ${import_picocolors.default.dim(`(${regionStr} grid, rough)`)}
|
|
2330
2303
|
${import_picocolors.default.dim("│")}
|
|
2331
|
-
${import_picocolors.default.dim("│")}
|
|
2332
|
-
${import_picocolors.default.dim("│")} ${cacheText.split(`
|
|
2304
|
+
${import_picocolors.default.dim("│")} ${cacheVerdict} — ${cacheText.split(`
|
|
2333
2305
|
`).join(`
|
|
2334
|
-
` + import_picocolors.default.dim("│") + "
|
|
2306
|
+
` + import_picocolors.default.dim("│") + " ")}
|
|
2335
2307
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2336
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black("
|
|
2337
|
-
${import_picocolors.default.dim("│")}
|
|
2338
|
-
${import_picocolors.default.dim("│")}
|
|
2308
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" YOUR LIMIT ")))}
|
|
2309
|
+
${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
|
|
2310
|
+
${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
|
|
2339
2311
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2340
|
-
${import_picocolors.default.dim("│")}
|
|
2341
|
-
${import_picocolors.default.dim("│")}
|
|
2342
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("code becomes commoditized by algorithms."))}
|
|
2343
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.italic(import_picocolors.default.dim("human mastery is the only true moat."))}
|
|
2344
|
-
${import_picocolors.default.dim("│")}
|
|
2345
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.cyan("***STAY VIGILANT***"))}
|
|
2312
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("Run this before you start. Keep the skill while you"))}
|
|
2313
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(import_picocolors.default.italic("use the speed."))}
|
|
2346
2314
|
${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
|
|
2347
2315
|
} else {
|
|
2348
2316
|
note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rosh100yx/outlier",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.24",
|
|
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
|
@@ -13,10 +13,12 @@ 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 {
|
|
@@ -30,13 +32,13 @@ export class ClaudeLogParser implements TokenLogParser {
|
|
|
30
32
|
try {
|
|
31
33
|
await access(logPath);
|
|
32
34
|
} catch {
|
|
33
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
35
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const text = await readFile(logPath, 'utf-8');
|
|
37
39
|
const lines = text.trim().split('\n').filter(l => l.length > 0);
|
|
38
|
-
|
|
39
|
-
let total = 0, output = 0, cache = 0;
|
|
40
|
+
|
|
41
|
+
let total = 0, output = 0, cache = 0, cost = 0;
|
|
40
42
|
const sessions = new Set<string>();
|
|
41
43
|
|
|
42
44
|
for (const line of lines) {
|
|
@@ -45,19 +47,27 @@ export class ClaudeLogParser implements TokenLogParser {
|
|
|
45
47
|
total += data.total_tokens || 0;
|
|
46
48
|
output += data.output_tokens || 0;
|
|
47
49
|
cache += data.cache_read || 0;
|
|
50
|
+
cost += data.cost_usd || 0; // present when the log was written with a cost field
|
|
48
51
|
if (data.session_id) sessions.add(data.session_id);
|
|
49
52
|
} catch (e) {}
|
|
50
53
|
}
|
|
51
|
-
return { total, output, cache, sessions: sessions.size };
|
|
54
|
+
return { total, output, cache, sessions: sessions.size, cost };
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
class CursorLogParser implements TokenLogParser {
|
|
56
59
|
async parse() {
|
|
57
|
-
return { total: 0, output: 0, cache: 0, sessions: 0 };
|
|
60
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
// Rough blended token pricing (USD per 1M tokens) for when the log has no cost field.
|
|
65
|
+
// Conservative mid-points across current models; labeled "rough" in the UI.
|
|
66
|
+
function estimateUsd(output: number, cacheRead: number, total: number): number {
|
|
67
|
+
const otherInput = Math.max(0, total - output - cacheRead);
|
|
68
|
+
return (output / 1e6) * 9 + (cacheRead / 1e6) * 0.3 + (otherInput / 1e6) * 3;
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
function getLocalGridFactor(): { region: string, factor: number } {
|
|
62
72
|
try {
|
|
63
73
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -73,7 +83,7 @@ function getLocalGridFactor(): { region: string, factor: number } {
|
|
|
73
83
|
export async function getCarbonStats(): Promise<CarbonStats> {
|
|
74
84
|
const parsers: TokenLogParser[] = [new ClaudeLogParser(), new CursorLogParser()];
|
|
75
85
|
|
|
76
|
-
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0;
|
|
86
|
+
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
|
|
77
87
|
|
|
78
88
|
for (const parser of parsers) {
|
|
79
89
|
const stats = await parser.parse();
|
|
@@ -81,11 +91,16 @@ export async function getCarbonStats(): Promise<CarbonStats> {
|
|
|
81
91
|
outputTokens += stats.output;
|
|
82
92
|
cacheReadTokens += stats.cache;
|
|
83
93
|
sessions += stats.sessions;
|
|
94
|
+
loggedCost += stats.cost;
|
|
84
95
|
}
|
|
85
96
|
|
|
86
97
|
const energyKwh = (outputTokens / 1_000_000) * 0.662;
|
|
87
98
|
const localGrid = getLocalGridFactor();
|
|
88
99
|
|
|
100
|
+
// Prefer the log's own cost field (accurate); fall back to a rough token estimate.
|
|
101
|
+
const costIsReal = loggedCost > 0;
|
|
102
|
+
const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
|
|
103
|
+
|
|
89
104
|
return {
|
|
90
105
|
totalTokens,
|
|
91
106
|
outputTokens,
|
|
@@ -95,6 +110,8 @@ export async function getCarbonStats(): Promise<CarbonStats> {
|
|
|
95
110
|
co2KgFrance: (energyKwh * gridFactors.france) / 1000,
|
|
96
111
|
localCo2Kg: (energyKwh * localGrid.factor) / 1000,
|
|
97
112
|
localRegion: localGrid.region,
|
|
98
|
-
sessions
|
|
113
|
+
sessions,
|
|
114
|
+
estUsd,
|
|
115
|
+
costIsReal
|
|
99
116
|
};
|
|
100
117
|
}
|
package/src/cli.ts
CHANGED
|
@@ -21,7 +21,6 @@ const ASCII_LOGO = `
|
|
|
21
21
|
let finalReceipt = '';
|
|
22
22
|
|
|
23
23
|
async function runOnboarding() {
|
|
24
|
-
console.clear();
|
|
25
24
|
console.log(pc.cyan(ASCII_LOGO));
|
|
26
25
|
intro(pc.inverse(' outlier: Welcome '));
|
|
27
26
|
|
|
@@ -79,7 +78,6 @@ async function main() {
|
|
|
79
78
|
action = 'status';
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
console.clear();
|
|
83
81
|
console.log(pc.cyan(ASCII_LOGO));
|
|
84
82
|
const pkg = require('../package.json');
|
|
85
83
|
console.log(pc.dim(` Outlier v${pkg.version} · AI Code Reliance & Telemetry Engine\n`));
|
|
@@ -278,29 +276,12 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
278
276
|
try {
|
|
279
277
|
let authPct = '0%';
|
|
280
278
|
let ruleFailures = 0;
|
|
281
|
-
|
|
282
|
-
let wittyRemark = isStrict ? '' : 'No git history (・_・ヾ';
|
|
283
|
-
let mentorString = '';
|
|
284
|
-
|
|
279
|
+
|
|
285
280
|
if (gitStats) {
|
|
286
281
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
287
|
-
|
|
288
|
-
if (!isStrict) {
|
|
289
|
-
if (gitStats.ratio < 0.1) wittyRemark = 'Artisan, hand-crafted code. Very 2019 of you (=^ ◡ ^=)';
|
|
290
|
-
else if (gitStats.ratio < 0.6) wittyRemark = 'A true centaur. Half human, half matrix (=`ω´=)';
|
|
291
|
-
else if (gitStats.ratio < 0.95) wittyRemark = 'Orchestrating the swarm. You are the manager now (ФДФ)';
|
|
292
|
-
else wittyRemark = '100% Cybernetic. Codebase goes brrrrr (=ಠᆽಠ=)';
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (gitStats.ratio > 0.7) {
|
|
296
|
-
authWarning = pc.red(isStrict ? `⚠ High Risk Surface: ${authPct} AI-generated. Human review required.` : `⚠ Mentoring Emergency: ${authPct} AI-generated. High risk of skill atrophy.`);
|
|
297
|
-
if (!isStrict) {
|
|
298
|
-
mentorString = `\n mentor: ${pc.blue('💡 Architecture Challenge Pending (See Git Hook)')}`;
|
|
299
|
-
}
|
|
300
|
-
ruleFailures++;
|
|
301
|
-
}
|
|
282
|
+
if (gitStats.ratio > 0.7) ruleFailures++;
|
|
302
283
|
}
|
|
303
|
-
|
|
284
|
+
|
|
304
285
|
let cachePct = '0';
|
|
305
286
|
let co2Str = '0.0kg';
|
|
306
287
|
let regionStr = 'Global Average';
|
|
@@ -312,44 +293,35 @@ Conservative Floor: ${color(nmPct + '%')}`,
|
|
|
312
293
|
regionStr = carbon.localRegion;
|
|
313
294
|
}
|
|
314
295
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const costIcon = isStrict ? '' : '(O_O;) ';
|
|
319
|
-
const failIcon = isStrict ? '⚠' : '(=ಠᆽಠ=)';
|
|
320
|
-
const passIcon = isStrict ? '✓' : '(=^ ◡ ^=)';
|
|
321
|
-
|
|
322
|
-
note(
|
|
323
|
-
`${capIcon}${pc.dim('[1] Capability Engine')} ${pc.cyan('▰▰▰▰▰▰▱▱▱▱')} ${pc.bold('Active')}
|
|
324
|
-
status: ${pc.green('✓ Configured')}
|
|
325
|
-
${authIcon}${pc.dim('[2] AI Code Reliance')} ${pc.yellow('▰▰▰▰▰▰▰▰▱▱')} ${pc.bold(`${authPct} Reliance`)}${vibeRow}
|
|
326
|
-
gate: ${gitStats && gitStats.ratio <= 0.7 ? pc.green('✓ Human Mastery Sustained') : `${pc.red(`${failIcon} Deskilling Risk Detected`)} ${pc.red('⚠ Security Audit Required')}`}${mentorString}
|
|
327
|
-
${costIcon}${pc.dim('[3] Tokenomics & Cost')} ${pc.magenta('▰▰▰▰▰▰▰▰▰▱')} ${pc.bold(`${cachePct}% Cache Bloat`)}
|
|
328
|
-
waste: ${pc.yellow(`⚠ ${cachePct}% of tokens are redundant context reads`)}
|
|
329
|
-
carbon: ${pc.green(`✓ ${co2Str} (Est. ${regionStr} Grid)`)}
|
|
330
|
-
${pc.bold('Governance:')} ${ruleFailures > 0 ? pc.red(`${failIcon} ${ruleFailures + 1} policy failures`) : pc.green(`${passIcon} All clear`)}`,
|
|
331
|
-
`${pc.bold('[outlier]')} ${5 - (ruleFailures+1)}/5 policies • ${authWarning || pc.green(`${passIcon} safe surface`)} • ${co2Str}`
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
296
|
+
// The thermal receipt below is the single canonical output for `status`.
|
|
297
|
+
// (The old @clack dashboard panel was removed: it duplicated the receipt's
|
|
298
|
+
// numbers in a second format, doubling the output on every run.)
|
|
335
299
|
const isDanger = gitStats && gitStats.ratio > 0.7;
|
|
336
|
-
const verdictZone = isDanger ? pc.red('
|
|
337
|
-
const verdictText = isDanger
|
|
338
|
-
? `
|
|
339
|
-
: `You
|
|
340
|
-
|
|
300
|
+
const verdictZone = isDanger ? pc.red('Mostly AI') : pc.green('You\'re driving');
|
|
301
|
+
const verdictText = isDanger
|
|
302
|
+
? `AI wrote most of this. Read it through so you can\n still debug it yourself when the agent isn't around.`
|
|
303
|
+
: `You're still writing the core of this. Good —\n that's how you keep the skill.`;
|
|
304
|
+
|
|
341
305
|
const isInefficient = parseFloat(cachePct) > 40;
|
|
342
|
-
const cacheVerdict = isInefficient ? pc.yellow('
|
|
343
|
-
const cacheText = isInefficient
|
|
344
|
-
? `
|
|
345
|
-
: `
|
|
346
|
-
|
|
347
|
-
const policyStatus = ruleFailures > 0 ? pc.red('
|
|
348
|
-
const policyAction = ruleFailures > 0 ? '
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
306
|
+
const cacheVerdict = isInefficient ? pc.yellow('Lots of re-reads') : pc.green('Lean');
|
|
307
|
+
const cacheText = isInefficient
|
|
308
|
+
? `Most of your tokens just re-send old context.\n It's normal for agents, but it's most of the bill.`
|
|
309
|
+
: `Little wasted context. Your spend is mostly\n real work.`;
|
|
310
|
+
|
|
311
|
+
const policyStatus = ruleFailures > 0 ? pc.red('Over your limit') : pc.green('Within limit');
|
|
312
|
+
const policyAction = ruleFailures > 0 ? 'Heads up only — nothing was blocked.' : 'Nothing to do.';
|
|
313
|
+
|
|
314
|
+
const fmtTokens = (n: number) =>
|
|
315
|
+
n >= 1_000_000_000 ? (n / 1_000_000_000).toFixed(1) + 'B'
|
|
316
|
+
: n >= 1_000_000 ? (n / 1_000_000).toFixed(1) + 'M'
|
|
317
|
+
: n >= 1_000 ? (n / 1_000).toFixed(1) + 'k'
|
|
318
|
+
: String(n);
|
|
319
|
+
const totalTokensStr = carbon ? fmtTokens(carbon.totalTokens) : '0';
|
|
320
|
+
const estUsdStr = carbon
|
|
321
|
+
? '$' + carbon.estUsd.toFixed(2) + (carbon.costIsReal ? '' : pc.dim(' (rough)'))
|
|
322
|
+
: pc.dim('n/a');
|
|
323
|
+
const humanSov = gitStats ? ((1 - gitStats.ratio) * 100).toFixed(0) + '%' : '100%';
|
|
324
|
+
const authorshipStr = pc.bold(authPct) + (isDanger ? pc.red(' AI-written') : pc.green(' AI-written'));
|
|
353
325
|
|
|
354
326
|
const getProgressBar = (pct: number, length = 10) => {
|
|
355
327
|
const filled = Math.max(0, Math.min(length, Math.round((pct / 100) * length)));
|
|
@@ -368,34 +340,29 @@ ${pc.bold('Governance:')} ${ruleFailures > 0 ? pc.red(`${failIcon} ${ruleFailure
|
|
|
368
340
|
|
|
369
341
|
finalReceipt = `
|
|
370
342
|
${pc.dim('┌────────────────────────────────────────────────────────')}
|
|
371
|
-
${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold('::
|
|
372
|
-
${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`::
|
|
343
|
+
${pc.dim('│')} ${pc.cyan('█▀█ █░█ ▀█▀ █░░ █ █▀▀ █▀█')} ${pc.bold(':: CODE AUDIT')}
|
|
344
|
+
${pc.dim('│')} ${pc.cyan('█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄')} ${pc.dim(`:: ${repoName} · ${dateStr}`)}
|
|
373
345
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
374
|
-
${pc.dim('│')} ${pc.bold(pc.bgBlue('
|
|
375
|
-
${pc.dim('│')} AI
|
|
376
|
-
${pc.dim('│')}
|
|
346
|
+
${pc.dim('│')} ${pc.bold(pc.bgBlue(' WHO WROTE THE CODE '))}
|
|
347
|
+
${pc.dim('│')} AI ${aiBar} ${authorshipStr}
|
|
348
|
+
${pc.dim('│')} You ${humanBar} ${pc.bold(humanSov)}
|
|
377
349
|
${pc.dim('│')}
|
|
378
|
-
${pc.dim('│')}
|
|
379
|
-
${pc.dim('│')} ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
350
|
+
${pc.dim('│')} ${verdictZone} — ${verdictText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
380
351
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
381
|
-
${pc.dim('│')} ${pc.bold(pc.bgMagenta('
|
|
382
|
-
${pc.dim('│')} Tokens
|
|
383
|
-
${pc.dim('│')}
|
|
384
|
-
${pc.dim('│')}
|
|
352
|
+
${pc.dim('│')} ${pc.bold(pc.bgMagenta(' WHAT IT COST '))}
|
|
353
|
+
${pc.dim('│')} Tokens used ${pc.bold(totalTokensStr)}
|
|
354
|
+
${pc.dim('│')} Est. spend ${pc.bold(estUsdStr)}
|
|
355
|
+
${pc.dim('│')} Re-used context ${cacheBar} ${pc.bold(cachePct + '%')}
|
|
356
|
+
${pc.dim('│')} Energy ${pc.bold(co2Str)} ${pc.dim(`(${regionStr} grid, rough)`)}
|
|
385
357
|
${pc.dim('│')}
|
|
386
|
-
${pc.dim('│')}
|
|
387
|
-
${pc.dim('│')} ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
358
|
+
${pc.dim('│')} ${cacheVerdict} — ${cacheText.split('\n').join('\n ' + pc.dim('│') + ' ')}
|
|
388
359
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
389
|
-
${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black('
|
|
390
|
-
${pc.dim('│')}
|
|
391
|
-
${pc.dim('│')}
|
|
360
|
+
${pc.dim('│')} ${pc.bold(pc.bgYellow(pc.black(' YOUR LIMIT ')))}
|
|
361
|
+
${pc.dim('│')} AI cap ${pc.bold('70%')} ${pc.dim('· change with: outlier policy')}
|
|
362
|
+
${pc.dim('│')} Status ${policyStatus} ${pc.dim('·')} ${policyAction}
|
|
392
363
|
${pc.dim('├────────────────────────────────────────────────────────')}
|
|
393
|
-
${pc.dim('│')}
|
|
394
|
-
${pc.dim('│')}
|
|
395
|
-
${pc.dim('│')} ${pc.italic(pc.dim('code becomes commoditized by algorithms.'))}
|
|
396
|
-
${pc.dim('│')} ${pc.italic(pc.dim('human mastery is the only true moat.'))}
|
|
397
|
-
${pc.dim('│')}
|
|
398
|
-
${pc.dim('│')} ${pc.bold(pc.cyan('***STAY VIGILANT***'))}
|
|
364
|
+
${pc.dim('│')} ${pc.dim(pc.italic('Run this before you start. Keep the skill while you'))}
|
|
365
|
+
${pc.dim('│')} ${pc.dim(pc.italic('use the speed.'))}
|
|
399
366
|
${pc.dim('└────────────────────────────────────────────────────────')}`;
|
|
400
367
|
} else {
|
|
401
368
|
note(
|