@rosh100yx/outlier 0.4.24 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -4
- package/bin/outlier.js +529 -123
- package/bin/postinstall.js +18 -17
- package/data/grid-factors.json +16 -3
- package/package.json +1 -1
- package/src/capabilities.ts +98 -58
- package/src/carbon.ts +80 -20
- package/src/cli.ts +181 -34
- package/src/emissions.ts +69 -0
- package/src/sources.ts +110 -0
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.
|
|
168
|
+
version: "0.7.0",
|
|
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"
|
|
@@ -1798,37 +1798,236 @@ async function getAuthorshipStats(repoPath = process.cwd()) {
|
|
|
1798
1798
|
}
|
|
1799
1799
|
|
|
1800
1800
|
// src/carbon.ts
|
|
1801
|
-
import { homedir } from "os";
|
|
1802
|
-
import { join } from "path";
|
|
1803
|
-
import { readFile, access } from "fs/promises";
|
|
1801
|
+
import { homedir as homedir2 } from "os";
|
|
1802
|
+
import { join as join2 } from "path";
|
|
1803
|
+
import { readFile, access, readdir } from "fs/promises";
|
|
1804
1804
|
// data/grid-factors.json
|
|
1805
1805
|
var grid_factors_default = {
|
|
1806
1806
|
vietnam: 681,
|
|
1807
|
-
|
|
1808
|
-
|
|
1807
|
+
india_average: 715,
|
|
1808
|
+
indonesia: 650,
|
|
1809
|
+
china: 581,
|
|
1809
1810
|
singapore: 408,
|
|
1810
|
-
|
|
1811
|
+
japan: 470,
|
|
1812
|
+
south_korea: 415,
|
|
1813
|
+
australia: 510,
|
|
1814
|
+
us_east: 380,
|
|
1815
|
+
us_west: 210,
|
|
1816
|
+
canada: 120,
|
|
1817
|
+
brazil: 95,
|
|
1818
|
+
uk: 210,
|
|
1819
|
+
germany: 350,
|
|
1820
|
+
france: 21.7,
|
|
1821
|
+
norway: 28,
|
|
1822
|
+
sweden: 41,
|
|
1823
|
+
global_average: 450
|
|
1811
1824
|
};
|
|
1812
1825
|
|
|
1826
|
+
// src/emissions.ts
|
|
1827
|
+
var MODEL_ENERGY_KWH_PER_M_OUTPUT = {
|
|
1828
|
+
opus: 0.66,
|
|
1829
|
+
sonnet: 0.3,
|
|
1830
|
+
haiku: 0.1,
|
|
1831
|
+
"gpt-4": 0.55,
|
|
1832
|
+
"gpt-4o": 0.3,
|
|
1833
|
+
"gpt-5": 0.45,
|
|
1834
|
+
gemini: 0.35,
|
|
1835
|
+
flash: 0.1,
|
|
1836
|
+
local: 0.5,
|
|
1837
|
+
default: 0.45
|
|
1838
|
+
};
|
|
1839
|
+
function modelClass(modelId) {
|
|
1840
|
+
const m2 = (modelId || "").toLowerCase();
|
|
1841
|
+
if (m2.includes("opus"))
|
|
1842
|
+
return "opus";
|
|
1843
|
+
if (m2.includes("sonnet"))
|
|
1844
|
+
return "sonnet";
|
|
1845
|
+
if (m2.includes("haiku"))
|
|
1846
|
+
return "haiku";
|
|
1847
|
+
if (m2.includes("flash") || m2.includes("mini"))
|
|
1848
|
+
return "haiku";
|
|
1849
|
+
if (m2.includes("gpt-5"))
|
|
1850
|
+
return "gpt-5";
|
|
1851
|
+
if (m2.includes("gpt-4o"))
|
|
1852
|
+
return "gpt-4o";
|
|
1853
|
+
if (m2.includes("gpt-4"))
|
|
1854
|
+
return "gpt-4";
|
|
1855
|
+
if (m2.includes("gemini"))
|
|
1856
|
+
return "gemini";
|
|
1857
|
+
if (m2.includes("llama") || m2.includes("qwen") || m2.includes("mistral") || m2.includes("local"))
|
|
1858
|
+
return "local";
|
|
1859
|
+
return "default";
|
|
1860
|
+
}
|
|
1861
|
+
function energyKwhForModel(modelId, outputTokens) {
|
|
1862
|
+
const cls = modelClass(modelId);
|
|
1863
|
+
const coeff = MODEL_ENERGY_KWH_PER_M_OUTPUT[cls] ?? 0.45;
|
|
1864
|
+
return outputTokens / 1e6 * coeff;
|
|
1865
|
+
}
|
|
1866
|
+
function energyKwhByModel(outputByModel) {
|
|
1867
|
+
let kwh = 0;
|
|
1868
|
+
for (const [model, out] of Object.entries(outputByModel)) {
|
|
1869
|
+
kwh += energyKwhForModel(model, out);
|
|
1870
|
+
}
|
|
1871
|
+
return kwh;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/sources.ts
|
|
1875
|
+
import { homedir } from "os";
|
|
1876
|
+
import { join } from "path";
|
|
1877
|
+
import { existsSync } from "fs";
|
|
1878
|
+
import { execSync } from "child_process";
|
|
1879
|
+
var HOME = homedir();
|
|
1880
|
+
function hasCli(cmd) {
|
|
1881
|
+
try {
|
|
1882
|
+
execSync(`command -v ${cmd}`, { stdio: "ignore" });
|
|
1883
|
+
return true;
|
|
1884
|
+
} catch {
|
|
1885
|
+
return false;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
function hasPath(p2) {
|
|
1889
|
+
try {
|
|
1890
|
+
return existsSync(p2);
|
|
1891
|
+
} catch {
|
|
1892
|
+
return false;
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
function detectSources(cwd = process.cwd()) {
|
|
1896
|
+
const tools = [];
|
|
1897
|
+
const add = (t2) => {
|
|
1898
|
+
if (!tools.includes(t2))
|
|
1899
|
+
tools.push(t2);
|
|
1900
|
+
};
|
|
1901
|
+
const cliTools = {
|
|
1902
|
+
claude: "claude",
|
|
1903
|
+
cursor: "cursor",
|
|
1904
|
+
aider: "aider",
|
|
1905
|
+
gemini: "gemini",
|
|
1906
|
+
opencode: "opencode",
|
|
1907
|
+
cody: "cody",
|
|
1908
|
+
continue: "continue",
|
|
1909
|
+
codex: "codex"
|
|
1910
|
+
};
|
|
1911
|
+
for (const [name, cmd] of Object.entries(cliTools)) {
|
|
1912
|
+
if (hasCli(cmd))
|
|
1913
|
+
add(name);
|
|
1914
|
+
}
|
|
1915
|
+
for (const [name, dir] of Object.entries({
|
|
1916
|
+
claude: ".claude",
|
|
1917
|
+
cursor: ".cursor",
|
|
1918
|
+
gemini: ".gemini",
|
|
1919
|
+
codeium: ".codeium",
|
|
1920
|
+
continue: ".continue",
|
|
1921
|
+
aider: ".aider.conf.yml"
|
|
1922
|
+
})) {
|
|
1923
|
+
if (hasPath(join(HOME, dir)))
|
|
1924
|
+
add(name);
|
|
1925
|
+
}
|
|
1926
|
+
if (hasCli("codecarbon"))
|
|
1927
|
+
add("codecarbon");
|
|
1928
|
+
if (hasCli("ccusage"))
|
|
1929
|
+
add("ccusage");
|
|
1930
|
+
const slug = cwd.replace(/\//g, "-");
|
|
1931
|
+
const claudeProjectDir = join(HOME, ".claude", "projects", slug);
|
|
1932
|
+
const tokenomicsLog = join(HOME, ".claude", "tokenomics-log.jsonl");
|
|
1933
|
+
let tokenSource;
|
|
1934
|
+
if (hasPath(tokenomicsLog)) {
|
|
1935
|
+
tokenSource = { name: "caveman tokenomics log", provenance: "measured" };
|
|
1936
|
+
} else if (hasPath(claudeProjectDir)) {
|
|
1937
|
+
tokenSource = { name: "Claude Code transcripts", provenance: "estimated" };
|
|
1938
|
+
} else if (tools.includes("ccusage")) {
|
|
1939
|
+
tokenSource = { name: "ccusage", provenance: "estimated" };
|
|
1940
|
+
} else {
|
|
1941
|
+
tokenSource = { name: "none", provenance: "none" };
|
|
1942
|
+
}
|
|
1943
|
+
let carbonSource;
|
|
1944
|
+
const codecarbonData = hasPath(join(cwd, "emissions.csv")) || hasPath(join(HOME, ".codecarbon", "emissions.csv"));
|
|
1945
|
+
if (codecarbonData) {
|
|
1946
|
+
carbonSource = { name: "CodeCarbon emissions.csv", provenance: "measured" };
|
|
1947
|
+
} else if (tokenSource.provenance !== "none") {
|
|
1948
|
+
carbonSource = { name: "model+grid estimate", provenance: "estimated" };
|
|
1949
|
+
} else {
|
|
1950
|
+
carbonSource = { name: "none", provenance: "none" };
|
|
1951
|
+
}
|
|
1952
|
+
let capabilitySource;
|
|
1953
|
+
if (hasPath(join(HOME, ".claude", "settings.json")) || hasPath(join(cwd, "AGENTS.md")) || hasPath(join(cwd, ".mcp.json"))) {
|
|
1954
|
+
capabilitySource = { name: "local config (settings/AGENTS/MCP)", provenance: "measured" };
|
|
1955
|
+
} else {
|
|
1956
|
+
capabilitySource = { name: "none", provenance: "none" };
|
|
1957
|
+
}
|
|
1958
|
+
return { tools, tokenSource, carbonSource, capabilitySource };
|
|
1959
|
+
}
|
|
1960
|
+
function provLabel(s) {
|
|
1961
|
+
if (s.provenance === "none")
|
|
1962
|
+
return "no local data";
|
|
1963
|
+
return `${s.provenance} · ${s.name}`;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1813
1966
|
// src/carbon.ts
|
|
1814
1967
|
class ClaudeLogParser {
|
|
1815
1968
|
baseDir;
|
|
1816
|
-
|
|
1969
|
+
cwd;
|
|
1970
|
+
constructor(baseDir = homedir2(), cwd = process.cwd()) {
|
|
1817
1971
|
this.baseDir = baseDir;
|
|
1972
|
+
this.cwd = cwd;
|
|
1818
1973
|
}
|
|
1819
1974
|
async parse() {
|
|
1820
|
-
const
|
|
1975
|
+
const slug = this.cwd.replace(/\//g, "-");
|
|
1976
|
+
const projectDir = join2(this.baseDir, ".claude", "projects", slug);
|
|
1977
|
+
try {
|
|
1978
|
+
const files = (await readdir(projectDir)).filter((f2) => f2.endsWith(".jsonl"));
|
|
1979
|
+
if (files.length > 0) {
|
|
1980
|
+
let total2 = 0, output2 = 0, cache2 = 0;
|
|
1981
|
+
const sessions2 = new Set;
|
|
1982
|
+
const outputByModel2 = {};
|
|
1983
|
+
for (const file of files) {
|
|
1984
|
+
let text3 = "";
|
|
1985
|
+
try {
|
|
1986
|
+
text3 = await readFile(join2(projectDir, file), "utf-8");
|
|
1987
|
+
} catch {
|
|
1988
|
+
continue;
|
|
1989
|
+
}
|
|
1990
|
+
for (const line of text3.split(`
|
|
1991
|
+
`)) {
|
|
1992
|
+
if (!line.trim())
|
|
1993
|
+
continue;
|
|
1994
|
+
try {
|
|
1995
|
+
const d = JSON.parse(line);
|
|
1996
|
+
const msg = d.message || {};
|
|
1997
|
+
const u4 = msg.usage || d.usage;
|
|
1998
|
+
if (u4) {
|
|
1999
|
+
const inp = u4.input_tokens || 0;
|
|
2000
|
+
const out = u4.output_tokens || 0;
|
|
2001
|
+
const cr = u4.cache_read_input_tokens || 0;
|
|
2002
|
+
const cw = u4.cache_creation_input_tokens || 0;
|
|
2003
|
+
total2 += inp + out + cr + cw;
|
|
2004
|
+
output2 += out;
|
|
2005
|
+
cache2 += cr;
|
|
2006
|
+
const model = msg.model || "default";
|
|
2007
|
+
outputByModel2[model] = (outputByModel2[model] || 0) + out;
|
|
2008
|
+
}
|
|
2009
|
+
if (d.sessionId)
|
|
2010
|
+
sessions2.add(d.sessionId);
|
|
2011
|
+
} catch {}
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
return { total: total2, output: output2, cache: cache2, sessions: sessions2.size, cost: 0, outputByModel: outputByModel2 };
|
|
2015
|
+
}
|
|
2016
|
+
} catch {}
|
|
2017
|
+
const logPath = join2(this.baseDir, ".claude", "tokenomics-log.jsonl");
|
|
1821
2018
|
try {
|
|
1822
2019
|
await access(logPath);
|
|
1823
2020
|
} catch {
|
|
1824
|
-
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
2021
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0, outputByModel: {} };
|
|
1825
2022
|
}
|
|
1826
2023
|
const text2 = await readFile(logPath, "utf-8");
|
|
1827
|
-
const lines = text2.trim().split(`
|
|
1828
|
-
`).filter((l2) => l2.length > 0);
|
|
1829
2024
|
let total = 0, output = 0, cache = 0, cost = 0;
|
|
1830
2025
|
const sessions = new Set;
|
|
1831
|
-
|
|
2026
|
+
const outputByModel = {};
|
|
2027
|
+
for (const line of text2.split(`
|
|
2028
|
+
`)) {
|
|
2029
|
+
if (!line.trim())
|
|
2030
|
+
continue;
|
|
1832
2031
|
try {
|
|
1833
2032
|
const data = JSON.parse(line);
|
|
1834
2033
|
total += data.total_tokens || 0;
|
|
@@ -1837,15 +2036,17 @@ class ClaudeLogParser {
|
|
|
1837
2036
|
cost += data.cost_usd || 0;
|
|
1838
2037
|
if (data.session_id)
|
|
1839
2038
|
sessions.add(data.session_id);
|
|
1840
|
-
|
|
2039
|
+
const model = data.model || "default";
|
|
2040
|
+
outputByModel[model] = (outputByModel[model] || 0) + (data.output_tokens || 0);
|
|
2041
|
+
} catch {}
|
|
1841
2042
|
}
|
|
1842
|
-
return { total, output, cache, sessions: sessions.size, cost };
|
|
2043
|
+
return { total, output, cache, sessions: sessions.size, cost, outputByModel };
|
|
1843
2044
|
}
|
|
1844
2045
|
}
|
|
1845
2046
|
|
|
1846
2047
|
class CursorLogParser {
|
|
1847
2048
|
async parse() {
|
|
1848
|
-
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
|
|
2049
|
+
return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0, outputByModel: {} };
|
|
1849
2050
|
}
|
|
1850
2051
|
}
|
|
1851
2052
|
function estimateUsd(output, cacheRead, total) {
|
|
@@ -1866,11 +2067,12 @@ function getLocalGridFactor() {
|
|
|
1866
2067
|
if (tz.includes("Calcutta") || tz.includes("Kolkata") || tz.includes("Asia/Kabul"))
|
|
1867
2068
|
return { region: "India", factor: grid_factors_default.india_average };
|
|
1868
2069
|
} catch (e) {}
|
|
1869
|
-
return { region: "Global Average", factor:
|
|
2070
|
+
return { region: "Global Average", factor: grid_factors_default.global_average };
|
|
1870
2071
|
}
|
|
1871
2072
|
async function getCarbonStats() {
|
|
1872
2073
|
const parsers = [new ClaudeLogParser, new CursorLogParser];
|
|
1873
2074
|
let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
|
|
2075
|
+
const outputByModel = {};
|
|
1874
2076
|
for (const parser of parsers) {
|
|
1875
2077
|
const stats = await parser.parse();
|
|
1876
2078
|
totalTokens += stats.total;
|
|
@@ -1878,9 +2080,13 @@ async function getCarbonStats() {
|
|
|
1878
2080
|
cacheReadTokens += stats.cache;
|
|
1879
2081
|
sessions += stats.sessions;
|
|
1880
2082
|
loggedCost += stats.cost;
|
|
2083
|
+
for (const [m2, out] of Object.entries(stats.outputByModel)) {
|
|
2084
|
+
outputByModel[m2] = (outputByModel[m2] || 0) + out;
|
|
2085
|
+
}
|
|
1881
2086
|
}
|
|
1882
|
-
const energyKwh =
|
|
2087
|
+
const energyKwh = energyKwhByModel(outputByModel);
|
|
1883
2088
|
const localGrid = getLocalGridFactor();
|
|
2089
|
+
const sources = detectSources();
|
|
1884
2090
|
const costIsReal = loggedCost > 0;
|
|
1885
2091
|
const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
|
|
1886
2092
|
return {
|
|
@@ -1894,80 +2100,152 @@ async function getCarbonStats() {
|
|
|
1894
2100
|
localRegion: localGrid.region,
|
|
1895
2101
|
sessions,
|
|
1896
2102
|
estUsd,
|
|
1897
|
-
costIsReal
|
|
2103
|
+
costIsReal,
|
|
2104
|
+
tokenProvenance: sources.tokenSource.provenance,
|
|
2105
|
+
carbonProvenance: sources.carbonSource.provenance,
|
|
2106
|
+
sourceLabel: provLabel(sources.tokenSource)
|
|
1898
2107
|
};
|
|
1899
2108
|
}
|
|
1900
2109
|
|
|
1901
2110
|
// src/capabilities.ts
|
|
1902
|
-
import { homedir as
|
|
1903
|
-
import { join as
|
|
1904
|
-
import { existsSync, readdirSync } from "fs";
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
if (
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
if (
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2111
|
+
import { homedir as homedir3 } from "os";
|
|
2112
|
+
import { join as join3 } from "path";
|
|
2113
|
+
import { existsSync as existsSync2, readdirSync, readFileSync } from "fs";
|
|
2114
|
+
function classifyReach(name) {
|
|
2115
|
+
const n2 = name.toLowerCase();
|
|
2116
|
+
if (/(stripe|payment|infinity|kucoin|wallet|billing|payout)/.test(n2))
|
|
2117
|
+
return "money";
|
|
2118
|
+
if (/(shell|bash|exec|terminal|command|sandbox)/.test(n2))
|
|
2119
|
+
return "exec";
|
|
2120
|
+
if (/(cloudflare|vercel|netlify|modal|deploy|fly\.io|render|heroku|aws|gcp|azure)/.test(n2))
|
|
2121
|
+
return "deploy";
|
|
2122
|
+
if (/(github|gitlab|git|bitbucket)/.test(n2))
|
|
2123
|
+
return "write-remote";
|
|
2124
|
+
if (/(filesystem|file|fs|disk)/.test(n2))
|
|
2125
|
+
return "write-local";
|
|
2126
|
+
if (/(memory|supermemory|mem|obsidian|notion|airtable|coda|database|sql|store)/.test(n2))
|
|
2127
|
+
return "data";
|
|
2128
|
+
if (/(openrouter|ollama|openai|anthropic|llm|model|hugging)/.test(n2))
|
|
2129
|
+
return "model";
|
|
2130
|
+
if (/(exa|web|fetch|search|brave|browser|http|scrape)/.test(n2))
|
|
2131
|
+
return "network";
|
|
2132
|
+
return "network";
|
|
2133
|
+
}
|
|
2134
|
+
var REACH_RISK = {
|
|
2135
|
+
read: 0,
|
|
2136
|
+
model: 1,
|
|
2137
|
+
network: 1,
|
|
2138
|
+
data: 2,
|
|
2139
|
+
"write-local": 2,
|
|
2140
|
+
"write-remote": 3,
|
|
2141
|
+
deploy: 3,
|
|
2142
|
+
exec: 4,
|
|
2143
|
+
money: 4
|
|
2144
|
+
};
|
|
2145
|
+
function scoreBlast(reaches) {
|
|
2146
|
+
const reasons = [];
|
|
2147
|
+
const has = (r2) => reaches.includes(r2);
|
|
2148
|
+
if (has("money"))
|
|
2149
|
+
reasons.push("can move money");
|
|
2150
|
+
if (has("exec"))
|
|
2151
|
+
reasons.push("can run shell commands");
|
|
2152
|
+
if (has("deploy"))
|
|
2153
|
+
reasons.push("can deploy to production");
|
|
2154
|
+
if (has("write-remote"))
|
|
2155
|
+
reasons.push("can push to your remote repos");
|
|
2156
|
+
if (has("write-local"))
|
|
2157
|
+
reasons.push("can write your local files");
|
|
2158
|
+
if (has("data"))
|
|
2159
|
+
reasons.push("can read/write your stored data");
|
|
2160
|
+
const netCount = reaches.filter((r2) => r2 === "network" || r2 === "model").length;
|
|
2161
|
+
if (netCount >= 3)
|
|
2162
|
+
reasons.push(`reaches ${netCount} external services`);
|
|
2163
|
+
const max = reaches.reduce((m2, r2) => Math.max(m2, REACH_RISK[r2]), 0);
|
|
2164
|
+
let radius = "LOW";
|
|
2165
|
+
if (max >= 4)
|
|
2166
|
+
radius = "CRITICAL";
|
|
2167
|
+
else if (max >= 3)
|
|
2168
|
+
radius = "HIGH";
|
|
2169
|
+
else if (max >= 2)
|
|
2170
|
+
radius = "MEDIUM";
|
|
2171
|
+
return { radius, reasons };
|
|
2172
|
+
}
|
|
2173
|
+
function readJson(path) {
|
|
2174
|
+
try {
|
|
2175
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
2176
|
+
} catch {
|
|
2177
|
+
return null;
|
|
1920
2178
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
stats.skills.push(skill);
|
|
1928
|
-
}
|
|
1929
|
-
} catch (e) {}
|
|
2179
|
+
}
|
|
2180
|
+
function countDir(path, ext = ".md") {
|
|
2181
|
+
try {
|
|
2182
|
+
return readdirSync(path).filter((f2) => f2.endsWith(ext)).length;
|
|
2183
|
+
} catch {
|
|
2184
|
+
return 0;
|
|
1930
2185
|
}
|
|
1931
|
-
|
|
1932
|
-
|
|
2186
|
+
}
|
|
2187
|
+
async function getCapabilitiesStats(repoPath = process.cwd(), homeDirPath = homedir3()) {
|
|
2188
|
+
const mcpNames = new Set;
|
|
2189
|
+
for (const cfg of [
|
|
2190
|
+
join3(homeDirPath, ".claude.json"),
|
|
2191
|
+
join3(homeDirPath, ".claude", "settings.json"),
|
|
2192
|
+
join3(repoPath, ".mcp.json"),
|
|
2193
|
+
join3(repoPath, ".claude", "settings.json")
|
|
2194
|
+
]) {
|
|
2195
|
+
const j = readJson(cfg);
|
|
2196
|
+
if (j?.mcpServers)
|
|
2197
|
+
Object.keys(j.mcpServers).forEach((k) => mcpNames.add(k));
|
|
2198
|
+
}
|
|
2199
|
+
const geminiMcp = join3(homeDirPath, ".gemini", "antigravity-cli", "mcp");
|
|
2200
|
+
if (existsSync2(geminiMcp)) {
|
|
1933
2201
|
try {
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
} catch (e) {}
|
|
2202
|
+
readdirSync(geminiMcp, { withFileTypes: true }).filter((d) => d.isDirectory()).forEach((d) => mcpNames.add(d.name));
|
|
2203
|
+
} catch {}
|
|
1937
2204
|
}
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
if (
|
|
1945
|
-
|
|
1946
|
-
}
|
|
2205
|
+
const mcps = [...mcpNames].map((name) => ({ name, reach: classifyReach(name) }));
|
|
2206
|
+
const skills = [];
|
|
2207
|
+
for (const p2 of [join3(repoPath, ".agents", "skills"), join3(homeDirPath, ".claude", "skills"), join3(homeDirPath, ".gemini", "skills")]) {
|
|
2208
|
+
if (existsSync2(p2)) {
|
|
2209
|
+
try {
|
|
2210
|
+
readdirSync(p2, { withFileTypes: true }).filter((d) => d.isDirectory()).forEach((d) => {
|
|
2211
|
+
if (!skills.includes(d.name))
|
|
2212
|
+
skills.push(d.name);
|
|
1947
2213
|
});
|
|
1948
|
-
}
|
|
1949
|
-
}
|
|
2214
|
+
} catch {}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const subagents = countDir(join3(homeDirPath, ".claude", "agents")) + countDir(join3(repoPath, ".claude", "agents"));
|
|
2218
|
+
const hooks = [];
|
|
2219
|
+
for (const cfg of [join3(homeDirPath, ".claude", "settings.json"), join3(repoPath, ".claude", "settings.json")]) {
|
|
2220
|
+
const j = readJson(cfg);
|
|
2221
|
+
if (j?.hooks)
|
|
2222
|
+
Object.keys(j.hooks).forEach((k) => {
|
|
2223
|
+
if (!hooks.includes(k))
|
|
2224
|
+
hooks.push(k);
|
|
2225
|
+
});
|
|
1950
2226
|
}
|
|
1951
|
-
|
|
2227
|
+
const hasOrchestration = existsSync2(join3(repoPath, "AGENTS.md")) || existsSync2(join3(repoPath, ".mcp.json"));
|
|
2228
|
+
const { radius, reasons } = scoreBlast(mcps.map((m2) => m2.reach));
|
|
2229
|
+
return { mcps, skills, subagents, hooks, hasOrchestration, blastRadius: radius, blastReasons: reasons };
|
|
1952
2230
|
}
|
|
1953
2231
|
|
|
1954
2232
|
// src/cli.ts
|
|
1955
|
-
import { writeFileSync, readFileSync as
|
|
1956
|
-
import { join as
|
|
2233
|
+
import { writeFileSync, readFileSync as readFileSync3, chmodSync, existsSync as existsSync4 } from "fs";
|
|
2234
|
+
import { join as join5 } from "path";
|
|
1957
2235
|
|
|
1958
2236
|
// src/agent.ts
|
|
1959
2237
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1960
|
-
import { readFileSync, existsSync as
|
|
1961
|
-
import { join as
|
|
2238
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
2239
|
+
import { join as join4 } from "path";
|
|
1962
2240
|
import os from "os";
|
|
1963
2241
|
function detectAgent() {
|
|
1964
2242
|
const agents = ["claude", "cursor", "aider", "hermes", "cody", "continue", "opencode", "gemini"];
|
|
1965
2243
|
try {
|
|
1966
2244
|
const home = os.homedir();
|
|
1967
|
-
const historyFiles = [
|
|
2245
|
+
const historyFiles = [join4(home, ".zsh_history"), join4(home, ".bash_history")];
|
|
1968
2246
|
for (const file of historyFiles) {
|
|
1969
|
-
if (
|
|
1970
|
-
const content =
|
|
2247
|
+
if (existsSync3(file)) {
|
|
2248
|
+
const content = readFileSync2(file, "utf8");
|
|
1971
2249
|
const lines = content.split(`
|
|
1972
2250
|
`).filter(Boolean).slice(-500).reverse();
|
|
1973
2251
|
for (const line of lines) {
|
|
@@ -2004,6 +2282,66 @@ var ASCII_LOGO = `
|
|
|
2004
2282
|
\\____/|_| |_| |_| |_| |_|___|_____|_| \\_\\
|
|
2005
2283
|
`;
|
|
2006
2284
|
var finalReceipt = "";
|
|
2285
|
+
async function emitJson() {
|
|
2286
|
+
const pkg = require_package();
|
|
2287
|
+
const [gitStats, carbon, caps] = await Promise.all([
|
|
2288
|
+
getAuthorshipStats().catch(() => null),
|
|
2289
|
+
getCarbonStats().catch(() => null),
|
|
2290
|
+
getCapabilitiesStats().catch(() => null)
|
|
2291
|
+
]);
|
|
2292
|
+
const aiRatio = gitStats ? gitStats.ratio : 0;
|
|
2293
|
+
const cap = 0.7;
|
|
2294
|
+
const writeOrDeploy = caps ? caps.mcps.filter((m2) => ["money", "exec", "deploy", "write-remote", "write-local"].includes(m2.reach)).length : 0;
|
|
2295
|
+
const out = {
|
|
2296
|
+
tool: "outlier",
|
|
2297
|
+
version: pkg.version,
|
|
2298
|
+
repo: process.cwd().split("/").pop(),
|
|
2299
|
+
generatedAt: new Date().toISOString(),
|
|
2300
|
+
localFirst: true,
|
|
2301
|
+
authorship: gitStats ? {
|
|
2302
|
+
aiPercent: +(gitStats.ratio * 100).toFixed(1),
|
|
2303
|
+
aiRatio: gitStats.ratio,
|
|
2304
|
+
totalCommits: gitStats.total,
|
|
2305
|
+
aiCommits: gitStats.ai,
|
|
2306
|
+
nonMergePercent: +(gitStats.ratioNoMerges * 100).toFixed(1),
|
|
2307
|
+
provenance: "proxy",
|
|
2308
|
+
note: "git Co-Authored-By trailers; under-counts if the agent omits the trailer"
|
|
2309
|
+
} : null,
|
|
2310
|
+
cost: carbon ? {
|
|
2311
|
+
totalTokens: carbon.totalTokens,
|
|
2312
|
+
outputTokens: carbon.outputTokens,
|
|
2313
|
+
cacheReusePercent: carbon.totalTokens ? +(carbon.cacheReadTokens / carbon.totalTokens * 100).toFixed(1) : 0,
|
|
2314
|
+
estUsd: +carbon.estUsd.toFixed(2),
|
|
2315
|
+
costIsReal: carbon.costIsReal,
|
|
2316
|
+
provenance: carbon.tokenProvenance,
|
|
2317
|
+
source: carbon.sourceLabel
|
|
2318
|
+
} : null,
|
|
2319
|
+
carbon: carbon ? {
|
|
2320
|
+
energyKwh: +carbon.energyKwh.toFixed(4),
|
|
2321
|
+
co2Kg: +carbon.localCo2Kg.toFixed(4),
|
|
2322
|
+
region: carbon.localRegion,
|
|
2323
|
+
provenance: carbon.carbonProvenance,
|
|
2324
|
+
note: "counterfactual: cloud inference runs on the provider grid, not yours"
|
|
2325
|
+
} : null,
|
|
2326
|
+
reach: caps ? {
|
|
2327
|
+
blastRadius: caps.blastRadius,
|
|
2328
|
+
reasons: caps.blastReasons,
|
|
2329
|
+
toolCount: caps.mcps.length,
|
|
2330
|
+
writeOrDeployCount: writeOrDeploy,
|
|
2331
|
+
tools: caps.mcps,
|
|
2332
|
+
subagents: caps.subagents,
|
|
2333
|
+
hooks: caps.hooks,
|
|
2334
|
+
skills: caps.skills.length,
|
|
2335
|
+
orchestration: caps.hasOrchestration
|
|
2336
|
+
} : null,
|
|
2337
|
+
policy: {
|
|
2338
|
+
aiCapPercent: cap * 100,
|
|
2339
|
+
status: aiRatio > cap ? "over" : "within"
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
process.stdout.write(JSON.stringify(out, null, 2) + `
|
|
2343
|
+
`);
|
|
2344
|
+
}
|
|
2007
2345
|
async function runOnboarding() {
|
|
2008
2346
|
console.log(import_picocolors.default.cyan(ASCII_LOGO));
|
|
2009
2347
|
intro(import_picocolors.default.inverse(" outlier: Welcome "));
|
|
@@ -2025,18 +2363,18 @@ As agents write more of our code, we lose visibility into:
|
|
|
2025
2363
|
cancel("Onboarding paused. Run outlier again when you are ready.");
|
|
2026
2364
|
process.exit(0);
|
|
2027
2365
|
}
|
|
2028
|
-
const configPath =
|
|
2366
|
+
const configPath = join5(os2.homedir(), ".outlier_config");
|
|
2029
2367
|
writeFileSync(configPath, JSON.stringify({ onboarded: true, date: new Date().toISOString() }));
|
|
2030
2368
|
}
|
|
2031
2369
|
async function main() {
|
|
2032
2370
|
let action = process.argv[2];
|
|
2033
2371
|
if (action === "daily-greeting") {
|
|
2034
|
-
const configPath2 =
|
|
2372
|
+
const configPath2 = join5(os2.homedir(), ".outlier_config");
|
|
2035
2373
|
const today = new Date().toISOString().split("T")[0];
|
|
2036
2374
|
let alreadyRun = false;
|
|
2037
|
-
if (
|
|
2375
|
+
if (existsSync4(configPath2)) {
|
|
2038
2376
|
try {
|
|
2039
|
-
const cfg = JSON.parse(
|
|
2377
|
+
const cfg = JSON.parse(readFileSync3(configPath2, "utf8"));
|
|
2040
2378
|
if (cfg.lastGreetingDate === today) {
|
|
2041
2379
|
alreadyRun = true;
|
|
2042
2380
|
} else {
|
|
@@ -2049,29 +2387,41 @@ async function main() {
|
|
|
2049
2387
|
process.exit(0);
|
|
2050
2388
|
action = "status";
|
|
2051
2389
|
}
|
|
2390
|
+
if (process.argv.includes("--json")) {
|
|
2391
|
+
await emitJson();
|
|
2392
|
+
process.exit(0);
|
|
2393
|
+
}
|
|
2052
2394
|
console.log(import_picocolors.default.cyan(ASCII_LOGO));
|
|
2053
2395
|
const pkg = require_package();
|
|
2054
2396
|
console.log(import_picocolors.default.dim(` Outlier v${pkg.version} · AI Code Reliance & Telemetry Engine
|
|
2055
2397
|
`));
|
|
2056
2398
|
if (action === "--help" || action === "-h" || action === "help") {
|
|
2057
2399
|
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
|
|
2400
|
+
WHAT OUTLIER DOES`));
|
|
2401
|
+
console.log(import_picocolors.default.dim(" Reads your local git history and AI logs — on your machine — to show"));
|
|
2402
|
+
console.log(import_picocolors.default.dim(` how much of your code AI wrote, what it cost, and how to keep your skill.
|
|
2403
|
+
`));
|
|
2404
|
+
console.log(import_picocolors.default.bold("COMMANDS:"));
|
|
2405
|
+
console.log(` ${import_picocolors.default.cyan("outlier")} Run the audit (the default — same as 'status')`);
|
|
2406
|
+
console.log(` ${import_picocolors.default.cyan("outlier status")} Full audit: who wrote the code, what it cost, your limit`);
|
|
2407
|
+
console.log(` ${import_picocolors.default.cyan("outlier status --save")} Save the audit to ./outlier-audit.txt`);
|
|
2408
|
+
console.log(` ${import_picocolors.default.cyan("outlier --json")} Machine-readable audit (for agents, CI, swarms)`);
|
|
2409
|
+
console.log(` ${import_picocolors.default.cyan("outlier authorship")} Just the AI-vs-human commit breakdown`);
|
|
2410
|
+
console.log(` ${import_picocolors.default.cyan("outlier carbon")} Just the token spend, cache waste & carbon`);
|
|
2411
|
+
console.log(` ${import_picocolors.default.cyan("outlier capabilities")} What tools & skills your agents can reach`);
|
|
2412
|
+
console.log(` ${import_picocolors.default.cyan("outlier policy")} Set an AI-authorship limit (local git hook / CI)`);
|
|
2413
|
+
console.log(` ${import_picocolors.default.cyan("outlier impact")} What AI reliance compounds to over time`);
|
|
2414
|
+
console.log(` ${import_picocolors.default.cyan("outlier knowledge")} The research behind the metrics`);
|
|
2415
|
+
console.log(` ${import_picocolors.default.cyan("outlier participate")} Share anonymous feedback for the deskilling study`);
|
|
2416
|
+
console.log(` ${import_picocolors.default.cyan("outlier init")} Show a once-per-day reliance greeting in new shells`);
|
|
2417
|
+
console.log(` ${import_picocolors.default.cyan("outlier uninit")} Remove that greeting`);
|
|
2069
2418
|
console.log(`
|
|
2070
|
-
` + import_picocolors.default.dim("
|
|
2419
|
+
` + import_picocolors.default.dim("Local-first: nothing ever leaves your machine."));
|
|
2420
|
+
console.log(import_picocolors.default.dim("How it works → https://github.com/rosh100yx/outlier#how-it-works"));
|
|
2071
2421
|
process.exit(0);
|
|
2072
2422
|
}
|
|
2073
|
-
const configPath =
|
|
2074
|
-
if (!
|
|
2423
|
+
const configPath = join5(os2.homedir(), ".outlier_config");
|
|
2424
|
+
if (!existsSync4(configPath) && !action) {
|
|
2075
2425
|
await runOnboarding();
|
|
2076
2426
|
action = "status";
|
|
2077
2427
|
}
|
|
@@ -2082,7 +2432,7 @@ COMMANDS:`));
|
|
|
2082
2432
|
if (action === "init" || action === "uninit") {
|
|
2083
2433
|
const shell = process.env.SHELL || "";
|
|
2084
2434
|
const rcName = shell.includes("zsh") ? ".zshrc" : ".bashrc";
|
|
2085
|
-
const rcPath =
|
|
2435
|
+
const rcPath = join5(os2.homedir(), rcName);
|
|
2086
2436
|
const START_MARKER = "# --- OUTLIER PRE-FLIGHT RITUAL START ---";
|
|
2087
2437
|
const END_MARKER = "# --- OUTLIER PRE-FLIGHT RITUAL END ---";
|
|
2088
2438
|
const BLOCK = `
|
|
@@ -2102,8 +2452,8 @@ ${END_MARKER}
|
|
|
2102
2452
|
process.exit(0);
|
|
2103
2453
|
}
|
|
2104
2454
|
let content = "";
|
|
2105
|
-
if (
|
|
2106
|
-
content =
|
|
2455
|
+
if (existsSync4(rcPath))
|
|
2456
|
+
content = readFileSync3(rcPath, "utf8");
|
|
2107
2457
|
if (content.includes(START_MARKER)) {
|
|
2108
2458
|
note(`Outlier is already initialized in ${rcName}`);
|
|
2109
2459
|
} else {
|
|
@@ -2112,8 +2462,8 @@ ${END_MARKER}
|
|
|
2112
2462
|
}
|
|
2113
2463
|
process.exit(0);
|
|
2114
2464
|
} else if (action === "uninit") {
|
|
2115
|
-
if (
|
|
2116
|
-
let content =
|
|
2465
|
+
if (existsSync4(rcPath)) {
|
|
2466
|
+
let content = readFileSync3(rcPath, "utf8");
|
|
2117
2467
|
if (content.includes(START_MARKER)) {
|
|
2118
2468
|
const regex = new RegExp(`\\n?${START_MARKER}[\\s\\S]*?${END_MARKER}\\n?`, "g");
|
|
2119
2469
|
content = content.replace(regex, `
|
|
@@ -2182,10 +2532,10 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2182
2532
|
let carbon = null;
|
|
2183
2533
|
let capabilities = null;
|
|
2184
2534
|
let skipDelay = false;
|
|
2185
|
-
const configPath2 =
|
|
2186
|
-
if (
|
|
2535
|
+
const configPath2 = join5(os2.homedir(), ".outlier_config");
|
|
2536
|
+
if (existsSync4(configPath2)) {
|
|
2187
2537
|
try {
|
|
2188
|
-
const cfg = JSON.parse(
|
|
2538
|
+
const cfg = JSON.parse(readFileSync3(configPath2, "utf8"));
|
|
2189
2539
|
if (cfg.seenNarration)
|
|
2190
2540
|
skipDelay = true;
|
|
2191
2541
|
} catch (e) {}
|
|
@@ -2223,7 +2573,7 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2223
2573
|
await new Promise((r2) => setTimeout(r2, 600));
|
|
2224
2574
|
if (!skipDelay) {
|
|
2225
2575
|
try {
|
|
2226
|
-
const cfg =
|
|
2576
|
+
const cfg = existsSync4(configPath2) ? JSON.parse(readFileSync3(configPath2, "utf8")) : {};
|
|
2227
2577
|
cfg.seenNarration = true;
|
|
2228
2578
|
writeFileSync(configPath2, JSON.stringify(cfg));
|
|
2229
2579
|
} catch (e) {}
|
|
@@ -2237,21 +2587,37 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2237
2587
|
s.stop("Audit complete");
|
|
2238
2588
|
try {
|
|
2239
2589
|
let authPct = "0%";
|
|
2590
|
+
let nmFloorStr = "";
|
|
2240
2591
|
let ruleFailures = 0;
|
|
2241
2592
|
if (gitStats) {
|
|
2242
2593
|
authPct = `${(gitStats.ratio * 100).toFixed(1)}%`;
|
|
2594
|
+
nmFloorStr = ` ${import_picocolors.default.dim(`(${(gitStats.ratioNoMerges * 100).toFixed(0)}% excl. merges)`)}`;
|
|
2243
2595
|
if (gitStats.ratio > 0.7)
|
|
2244
2596
|
ruleFailures++;
|
|
2245
2597
|
}
|
|
2598
|
+
const lowTrailerWarn = gitStats && gitStats.ratio < 0.1 && carbon && carbon.totalTokens > 1e6 ? `
|
|
2599
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Low %? Your agent may not tag commits — outlier counts only")}
|
|
2600
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("commits with a Co-Authored-By trailer.")}` : "";
|
|
2246
2601
|
let cachePct = "0";
|
|
2247
2602
|
let co2Str = "0.0kg";
|
|
2248
2603
|
let regionStr = "Global Average";
|
|
2604
|
+
let sourceLabel = "no local AI logs found";
|
|
2605
|
+
let noData = true;
|
|
2249
2606
|
if (carbon) {
|
|
2250
2607
|
if (carbon.totalTokens > 0) {
|
|
2251
2608
|
cachePct = (carbon.cacheReadTokens / carbon.totalTokens * 100).toFixed(1);
|
|
2609
|
+
noData = false;
|
|
2252
2610
|
}
|
|
2253
2611
|
co2Str = `${carbon.localCo2Kg.toFixed(2)}kg CO2`;
|
|
2254
2612
|
regionStr = carbon.localRegion;
|
|
2613
|
+
sourceLabel = carbon.sourceLabel;
|
|
2614
|
+
}
|
|
2615
|
+
let reachStr = import_picocolors.default.dim("run: outlier capabilities");
|
|
2616
|
+
if (capabilities) {
|
|
2617
|
+
const rc = capabilities.blastRadius;
|
|
2618
|
+
const col = rc === "CRITICAL" || rc === "HIGH" ? import_picocolors.default.red : rc === "MEDIUM" ? import_picocolors.default.yellow : import_picocolors.default.green;
|
|
2619
|
+
const risky = capabilities.mcps.filter((m2) => ["money", "exec", "deploy", "write-remote", "write-local"].includes(m2.reach)).length;
|
|
2620
|
+
reachStr = `${col(import_picocolors.default.bold(rc))} · ${capabilities.mcps.length} tools` + (risky ? import_picocolors.default.dim(`, ${risky} can write/deploy`) : "");
|
|
2255
2621
|
}
|
|
2256
2622
|
const isDanger = gitStats && gitStats.ratio > 0.7;
|
|
2257
2623
|
const verdictZone = isDanger ? import_picocolors.default.red("Mostly AI") : import_picocolors.default.green("You're driving");
|
|
@@ -2288,29 +2654,36 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2288
2654
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.cyan("█▄█ █▄█ ░█░ █▄▄ █ ██▄ █▀▄")} ${import_picocolors.default.dim(`:: ${repoName} · ${dateStr}`)}
|
|
2289
2655
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2290
2656
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgBlue(" WHO WROTE THE CODE "))}
|
|
2291
|
-
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}
|
|
2657
|
+
${import_picocolors.default.dim("│")} AI ${aiBar} ${authorshipStr}${nmFloorStr}
|
|
2292
2658
|
${import_picocolors.default.dim("│")} You ${humanBar} ${import_picocolors.default.bold(humanSov)}
|
|
2659
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Typical: solo devs 10–40% · AI-framework repos up to ~80%")}
|
|
2293
2660
|
${import_picocolors.default.dim("│")}
|
|
2294
2661
|
${import_picocolors.default.dim("│")} ${verdictZone} — ${verdictText.split(`
|
|
2295
2662
|
`).join(`
|
|
2296
|
-
` + import_picocolors.default.dim("│") + " ")}
|
|
2663
|
+
` + import_picocolors.default.dim("│") + " ")}${lowTrailerWarn}
|
|
2297
2664
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2298
2665
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgMagenta(" WHAT IT COST "))}
|
|
2299
2666
|
${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
|
|
2300
2667
|
${import_picocolors.default.dim("│")} Est. spend ${import_picocolors.default.bold(estUsdStr)}
|
|
2301
2668
|
${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
|
|
2669
|
+
${import_picocolors.default.dim("│")} Energy ${import_picocolors.default.bold(co2Str)} ${import_picocolors.default.dim(`(${regionStr} grid)`)}
|
|
2670
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(`Source: ${sourceLabel}`)}
|
|
2303
2671
|
${import_picocolors.default.dim("│")}
|
|
2304
2672
|
${import_picocolors.default.dim("│")} ${cacheVerdict} — ${cacheText.split(`
|
|
2305
2673
|
`).join(`
|
|
2306
2674
|
` + import_picocolors.default.dim("│") + " ")}
|
|
2307
2675
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2676
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgCyan(import_picocolors.default.black(" WHAT YOUR AGENTS CAN REACH ")))}
|
|
2677
|
+
${import_picocolors.default.dim("│")} Blast radius ${reachStr}
|
|
2678
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Full map (deploy/push/write tools): outlier capabilities")}
|
|
2679
|
+
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2308
2680
|
${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" YOUR LIMIT ")))}
|
|
2309
2681
|
${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
|
|
2310
2682
|
${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
|
|
2311
2683
|
${import_picocolors.default.dim("├────────────────────────────────────────────────────────")}
|
|
2312
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(
|
|
2313
|
-
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim(
|
|
2684
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("Numbers are local estimates — authorship is a proxy and")}
|
|
2685
|
+
${import_picocolors.default.dim("│")} ${import_picocolors.default.dim("carbon is rough. How it works: outlier --help")}
|
|
2686
|
+
${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
2687
|
${import_picocolors.default.dim("└────────────────────────────────────────────────────────")}`;
|
|
2315
2688
|
} else {
|
|
2316
2689
|
note(`status: ${authPct} AI Reliance | ${cachePct}% Cache Bloat | ${co2Str}`, `${import_picocolors.default.bold("[outlier]")} CI/CD Audit`);
|
|
@@ -2320,23 +2693,42 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
|
|
|
2320
2693
|
console.error(import_picocolors.default.red(e.message));
|
|
2321
2694
|
}
|
|
2322
2695
|
} else if (action === "capabilities") {
|
|
2323
|
-
s.start("
|
|
2696
|
+
s.start("Mapping what your agents can reach...");
|
|
2324
2697
|
try {
|
|
2325
2698
|
const caps = await getCapabilitiesStats();
|
|
2326
|
-
s.stop("
|
|
2327
|
-
|
|
2699
|
+
s.stop("Reach map complete");
|
|
2700
|
+
const radiusColor = caps.blastRadius === "CRITICAL" ? import_picocolors.default.red : caps.blastRadius === "HIGH" ? import_picocolors.default.red : caps.blastRadius === "MEDIUM" ? import_picocolors.default.yellow : import_picocolors.default.green;
|
|
2701
|
+
const order = ["money", "exec", "deploy", "write-remote", "write-local", "data", "network", "model", "read"];
|
|
2702
|
+
const reachLabel = {
|
|
2703
|
+
money: "can move money",
|
|
2704
|
+
exec: "can run shell",
|
|
2705
|
+
deploy: "can deploy",
|
|
2706
|
+
"write-remote": "can push to repos",
|
|
2707
|
+
"write-local": "can write files",
|
|
2708
|
+
data: "data stores",
|
|
2709
|
+
network: "network",
|
|
2710
|
+
model: "models",
|
|
2711
|
+
read: "read-only"
|
|
2712
|
+
};
|
|
2713
|
+
const riskyReaches = new Set(["money", "exec", "deploy", "write-remote", "write-local"]);
|
|
2714
|
+
const toolLines = caps.mcps.length === 0 ? " None detected" : order.filter((r2) => caps.mcps.some((m2) => m2.reach === r2)).map((r2) => {
|
|
2715
|
+
const names = caps.mcps.filter((m2) => m2.reach === r2).map((m2) => m2.name).join(", ");
|
|
2716
|
+
const tag = riskyReaches.has(r2) ? import_picocolors.default.red(`[${reachLabel[r2]}]`) : import_picocolors.default.dim(`[${reachLabel[r2]}]`);
|
|
2717
|
+
return ` ${tag} ${names}`;
|
|
2718
|
+
}).join(`
|
|
2719
|
+
`);
|
|
2720
|
+
note(`${import_picocolors.default.bold("BLAST RADIUS:")} ${radiusColor(import_picocolors.default.bold(caps.blastRadius))} ${import_picocolors.default.dim("— if an agent or a prompt injection drives your tools")}
|
|
2721
|
+
${caps.blastReasons.length ? caps.blastReasons.map((r2) => ` ${import_picocolors.default.red("•")} ${r2}`).join(`
|
|
2722
|
+
`) : import_picocolors.default.green(" • read-only — limited reach")}
|
|
2328
2723
|
|
|
2329
|
-
|
|
2330
|
-
${
|
|
2331
|
-
`)) : " None"}
|
|
2724
|
+
${import_picocolors.default.bold(`What your agents can reach (${caps.mcps.length} MCP tools):`)}
|
|
2725
|
+
${toolLines}
|
|
2332
2726
|
|
|
2333
|
-
|
|
2334
|
-
${caps.
|
|
2335
|
-
|
|
2727
|
+
${import_picocolors.default.bold("Automation & agents:")}
|
|
2728
|
+
Hooks that fire for you: ${caps.hooks.length ? import_picocolors.default.yellow(caps.hooks.join(", ")) : "none"}
|
|
2729
|
+
Sub-agents: ${caps.subagents} Skills: ${caps.skills.length} Orchestration policy: ${caps.hasOrchestration ? import_picocolors.default.green("yes") : import_picocolors.default.yellow("no")}
|
|
2336
2730
|
|
|
2337
|
-
${import_picocolors.default.
|
|
2338
|
-
This repository provides agents with ${caps.mcps.length} toolsets and ${caps.skills.length} skills.
|
|
2339
|
-
${caps.skills.length > 5 ? import_picocolors.default.red("⚠ High Surface Area: Ensure strict authorship review is enabled.") : import_picocolors.default.green("✓ Low Surface Area: Risk contained.")}`, "AI Capabilities Map");
|
|
2731
|
+
${import_picocolors.default.dim("This is your attack surface. Fewer write/deploy tools per session = smaller blast radius.")}`, "Agent Reach & Blast Radius");
|
|
2340
2732
|
} catch (e) {
|
|
2341
2733
|
s.stop("Audit failed");
|
|
2342
2734
|
console.error(import_picocolors.default.red(e.message));
|
|
@@ -2370,8 +2762,8 @@ ${caps.skills.length > 5 ? import_picocolors.default.red("⚠ High Surface Area:
|
|
|
2370
2762
|
process.exit(0);
|
|
2371
2763
|
}
|
|
2372
2764
|
s.start(`Applying ${tier} policy guardrails...`);
|
|
2373
|
-
const gitDir =
|
|
2374
|
-
const isRepo =
|
|
2765
|
+
const gitDir = join5(process.cwd(), ".git");
|
|
2766
|
+
const isRepo = existsSync4(gitDir);
|
|
2375
2767
|
if (!isRepo) {
|
|
2376
2768
|
console.error(import_picocolors.default.red("Must be run inside a git repository"));
|
|
2377
2769
|
process.exit(1);
|
|
@@ -2379,8 +2771,8 @@ ${caps.skills.length > 5 ? import_picocolors.default.red("⚠ High Surface Area:
|
|
|
2379
2771
|
const isStrict = process.argv.includes("--strict");
|
|
2380
2772
|
const bouncerMsg = isStrict ? `echo "⚠️ outlier policy warning: AI authorship ($CURRENT_RATIO%) exceeds threshold ($MAX_RATIO%)"` : `echo "\uD83D\uDEE1️ Outlier Bouncer: Repository AI-generation ($CURRENT_RATIO%) exceeds your defined mastery threshold ($MAX_RATIO%)."
|
|
2381
2773
|
echo "Take a moment to review your recent architectural decisions. Ensure you still understand the system."`;
|
|
2382
|
-
const hookPath =
|
|
2383
|
-
if (
|
|
2774
|
+
const hookPath = join5(gitDir, "hooks", "pre-commit");
|
|
2775
|
+
if (existsSync4(hookPath)) {
|
|
2384
2776
|
const { copyFileSync } = __require("fs");
|
|
2385
2777
|
copyFileSync(hookPath, `${hookPath}.backup`);
|
|
2386
2778
|
}
|
|
@@ -2413,7 +2805,7 @@ Enforcement: ${import_picocolors.default.cyan("Local pre-commit hook installed
|
|
|
2413
2805
|
} else if (tier === "regulatory") {
|
|
2414
2806
|
s.start("Generating Regulatory Compliance Audit (Decree 142)...");
|
|
2415
2807
|
await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
2416
|
-
const reportPath =
|
|
2808
|
+
const reportPath = join5(process.cwd(), "outlier-audit-report.jsonl");
|
|
2417
2809
|
writeFileSync(reportPath, JSON.stringify({ timestamp: new Date().toISOString(), status: "PREVIEW", policy: "Decree 142", simulatedOversight: true }) + `
|
|
2418
2810
|
`);
|
|
2419
2811
|
s.stop("Audit Generated");
|
|
@@ -2503,9 +2895,19 @@ ${import_picocolors.default.bold("Submit here (and drop your screenshot!):")} ${
|
|
|
2503
2895
|
Read the full academic foundation at: ${import_picocolors.default.underline("https://github.com/rosh100yx/outlier")}
|
|
2504
2896
|
`);
|
|
2505
2897
|
}
|
|
2506
|
-
outro("
|
|
2898
|
+
outro("Done — nothing left your machine. (How it works: outlier --help)");
|
|
2507
2899
|
if (typeof finalReceipt !== "undefined" && finalReceipt) {
|
|
2508
2900
|
console.log(finalReceipt);
|
|
2901
|
+
if (process.argv.includes("--save")) {
|
|
2902
|
+
const stripAnsi = (s2) => s2.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2903
|
+
const savePath = join5(process.cwd(), "outlier-audit.txt");
|
|
2904
|
+
try {
|
|
2905
|
+
writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + `
|
|
2906
|
+
`);
|
|
2907
|
+
console.log(import_picocolors.default.dim(`
|
|
2908
|
+
\uD83D\uDCBE Saved to ${savePath}`));
|
|
2909
|
+
} catch {}
|
|
2910
|
+
}
|
|
2509
2911
|
}
|
|
2510
2912
|
if (action === "status") {
|
|
2511
2913
|
const agent = detectAgent();
|
|
@@ -2516,10 +2918,14 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
|
|
|
2516
2918
|
console.log(import_picocolors.default.bold(import_picocolors.default.magenta(" ↳ Ready to code? ")) + "Start your AI agent");
|
|
2517
2919
|
}
|
|
2518
2920
|
console.log("");
|
|
2519
|
-
console.log(import_picocolors.default.bold(import_picocolors.default.
|
|
2520
|
-
console.log(import_picocolors.default.bold(import_picocolors.default.
|
|
2921
|
+
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"));
|
|
2922
|
+
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"));
|
|
2923
|
+
if (!process.argv.includes("--save")) {
|
|
2924
|
+
console.log(import_picocolors.default.dim(" \uD83D\uDCBE Save: outlier status --save"));
|
|
2925
|
+
}
|
|
2521
2926
|
console.log(import_picocolors.default.dim(`
|
|
2522
|
-
|
|
2927
|
+
outlier does more than this audit — see how you adopt AI, what it`));
|
|
2928
|
+
console.log(import_picocolors.default.dim(" costs, and what is actually working: ") + import_picocolors.default.bold(import_picocolors.default.cyan("outlier --help")));
|
|
2523
2929
|
}
|
|
2524
2930
|
}
|
|
2525
2931
|
main().catch(console.error);
|