@rosh100yx/outlier 0.4.25 → 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/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.25",
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,38 +1798,192 @@ 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";
1801
+ import { homedir as homedir2 } from "os";
1802
+ import { join as join2 } from "path";
1803
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
- france: 21.7,
1808
- us_east: 380,
1807
+ india_average: 715,
1808
+ indonesia: 650,
1809
+ china: 581,
1809
1810
  singapore: 408,
1810
- india_average: 715
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
1824
+ };
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
1811
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
+ }
1812
1965
 
1813
1966
  // src/carbon.ts
1814
1967
  class ClaudeLogParser {
1815
1968
  baseDir;
1816
1969
  cwd;
1817
- constructor(baseDir = homedir(), cwd = process.cwd()) {
1970
+ constructor(baseDir = homedir2(), cwd = process.cwd()) {
1818
1971
  this.baseDir = baseDir;
1819
1972
  this.cwd = cwd;
1820
1973
  }
1821
1974
  async parse() {
1822
1975
  const slug = this.cwd.replace(/\//g, "-");
1823
- const projectDir = join(this.baseDir, ".claude", "projects", slug);
1976
+ const projectDir = join2(this.baseDir, ".claude", "projects", slug);
1824
1977
  try {
1825
1978
  const files = (await readdir(projectDir)).filter((f2) => f2.endsWith(".jsonl"));
1826
1979
  if (files.length > 0) {
1827
1980
  let total2 = 0, output2 = 0, cache2 = 0;
1828
1981
  const sessions2 = new Set;
1982
+ const outputByModel2 = {};
1829
1983
  for (const file of files) {
1830
1984
  let text3 = "";
1831
1985
  try {
1832
- text3 = await readFile(join(projectDir, file), "utf-8");
1986
+ text3 = await readFile(join2(projectDir, file), "utf-8");
1833
1987
  } catch {
1834
1988
  continue;
1835
1989
  }
@@ -1839,7 +1993,8 @@ class ClaudeLogParser {
1839
1993
  continue;
1840
1994
  try {
1841
1995
  const d = JSON.parse(line);
1842
- const u4 = d.message && d.message.usage || d.usage;
1996
+ const msg = d.message || {};
1997
+ const u4 = msg.usage || d.usage;
1843
1998
  if (u4) {
1844
1999
  const inp = u4.input_tokens || 0;
1845
2000
  const out = u4.output_tokens || 0;
@@ -1848,24 +2003,27 @@ class ClaudeLogParser {
1848
2003
  total2 += inp + out + cr + cw;
1849
2004
  output2 += out;
1850
2005
  cache2 += cr;
2006
+ const model = msg.model || "default";
2007
+ outputByModel2[model] = (outputByModel2[model] || 0) + out;
1851
2008
  }
1852
2009
  if (d.sessionId)
1853
2010
  sessions2.add(d.sessionId);
1854
2011
  } catch {}
1855
2012
  }
1856
2013
  }
1857
- return { total: total2, output: output2, cache: cache2, sessions: sessions2.size, cost: 0 };
2014
+ return { total: total2, output: output2, cache: cache2, sessions: sessions2.size, cost: 0, outputByModel: outputByModel2 };
1858
2015
  }
1859
2016
  } catch {}
1860
- const logPath = join(this.baseDir, ".claude", "tokenomics-log.jsonl");
2017
+ const logPath = join2(this.baseDir, ".claude", "tokenomics-log.jsonl");
1861
2018
  try {
1862
2019
  await access(logPath);
1863
2020
  } catch {
1864
- return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
2021
+ return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0, outputByModel: {} };
1865
2022
  }
1866
2023
  const text2 = await readFile(logPath, "utf-8");
1867
2024
  let total = 0, output = 0, cache = 0, cost = 0;
1868
2025
  const sessions = new Set;
2026
+ const outputByModel = {};
1869
2027
  for (const line of text2.split(`
1870
2028
  `)) {
1871
2029
  if (!line.trim())
@@ -1878,15 +2036,17 @@ class ClaudeLogParser {
1878
2036
  cost += data.cost_usd || 0;
1879
2037
  if (data.session_id)
1880
2038
  sessions.add(data.session_id);
2039
+ const model = data.model || "default";
2040
+ outputByModel[model] = (outputByModel[model] || 0) + (data.output_tokens || 0);
1881
2041
  } catch {}
1882
2042
  }
1883
- return { total, output, cache, sessions: sessions.size, cost };
2043
+ return { total, output, cache, sessions: sessions.size, cost, outputByModel };
1884
2044
  }
1885
2045
  }
1886
2046
 
1887
2047
  class CursorLogParser {
1888
2048
  async parse() {
1889
- return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0 };
2049
+ return { total: 0, output: 0, cache: 0, sessions: 0, cost: 0, outputByModel: {} };
1890
2050
  }
1891
2051
  }
1892
2052
  function estimateUsd(output, cacheRead, total) {
@@ -1907,11 +2067,12 @@ function getLocalGridFactor() {
1907
2067
  if (tz.includes("Calcutta") || tz.includes("Kolkata") || tz.includes("Asia/Kabul"))
1908
2068
  return { region: "India", factor: grid_factors_default.india_average };
1909
2069
  } catch (e) {}
1910
- return { region: "Global Average", factor: 450 };
2070
+ return { region: "Global Average", factor: grid_factors_default.global_average };
1911
2071
  }
1912
2072
  async function getCarbonStats() {
1913
2073
  const parsers = [new ClaudeLogParser, new CursorLogParser];
1914
2074
  let totalTokens = 0, outputTokens = 0, cacheReadTokens = 0, sessions = 0, loggedCost = 0;
2075
+ const outputByModel = {};
1915
2076
  for (const parser of parsers) {
1916
2077
  const stats = await parser.parse();
1917
2078
  totalTokens += stats.total;
@@ -1919,9 +2080,13 @@ async function getCarbonStats() {
1919
2080
  cacheReadTokens += stats.cache;
1920
2081
  sessions += stats.sessions;
1921
2082
  loggedCost += stats.cost;
2083
+ for (const [m2, out] of Object.entries(stats.outputByModel)) {
2084
+ outputByModel[m2] = (outputByModel[m2] || 0) + out;
2085
+ }
1922
2086
  }
1923
- const energyKwh = outputTokens / 1e6 * 0.662;
2087
+ const energyKwh = energyKwhByModel(outputByModel);
1924
2088
  const localGrid = getLocalGridFactor();
2089
+ const sources = detectSources();
1925
2090
  const costIsReal = loggedCost > 0;
1926
2091
  const estUsd = costIsReal ? loggedCost : estimateUsd(outputTokens, cacheReadTokens, totalTokens);
1927
2092
  return {
@@ -1935,80 +2100,152 @@ async function getCarbonStats() {
1935
2100
  localRegion: localGrid.region,
1936
2101
  sessions,
1937
2102
  estUsd,
1938
- costIsReal
2103
+ costIsReal,
2104
+ tokenProvenance: sources.tokenSource.provenance,
2105
+ carbonProvenance: sources.carbonSource.provenance,
2106
+ sourceLabel: provLabel(sources.tokenSource)
1939
2107
  };
1940
2108
  }
1941
2109
 
1942
2110
  // src/capabilities.ts
1943
- import { homedir as homedir2 } from "os";
1944
- import { join as join2 } from "path";
1945
- import { existsSync, readdirSync } from "fs";
1946
- async function getCapabilitiesStats(repoPath = process.cwd(), homeDirPath = homedir2()) {
1947
- const stats = {
1948
- mcps: [],
1949
- skills: [],
1950
- hasOrchestration: false
1951
- };
1952
- if (existsSync(join2(repoPath, "AGENTS.md"))) {
1953
- stats.hasOrchestration = true;
1954
- }
1955
- const projectSkillsPath = join2(repoPath, ".agents", "skills");
1956
- if (existsSync(projectSkillsPath)) {
1957
- try {
1958
- const skills = readdirSync(projectSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1959
- stats.skills.push(...skills);
1960
- } catch (e) {}
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;
1961
2178
  }
1962
- const geminiSkillsPath = join2(homeDirPath, ".gemini", "skills");
1963
- if (existsSync(geminiSkillsPath)) {
1964
- try {
1965
- const skills = readdirSync(geminiSkillsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1966
- for (const skill of skills) {
1967
- if (!stats.skills.includes(skill))
1968
- stats.skills.push(skill);
1969
- }
1970
- } 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;
1971
2185
  }
1972
- const geminiMcpPath = join2(homeDirPath, ".gemini", "antigravity-cli", "mcp");
1973
- if (existsSync(geminiMcpPath)) {
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)) {
1974
2201
  try {
1975
- const mcps = readdirSync(geminiMcpPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
1976
- stats.mcps.push(...mcps);
1977
- } catch (e) {}
2202
+ readdirSync(geminiMcp, { withFileTypes: true }).filter((d) => d.isDirectory()).forEach((d) => mcpNames.add(d.name));
2203
+ } catch {}
1978
2204
  }
1979
- const claudeSettingsPath = join2(homeDirPath, ".claude", "settings.json");
1980
- if (existsSync(claudeSettingsPath)) {
1981
- try {
1982
- const claudeSettings = __require(claudeSettingsPath);
1983
- if (claudeSettings.enabledPlugins) {
1984
- Object.keys(claudeSettings.enabledPlugins).forEach((plugin) => {
1985
- if (claudeSettings.enabledPlugins[plugin] && !stats.mcps.includes(plugin)) {
1986
- stats.mcps.push(`plugin:${plugin}`);
1987
- }
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);
1988
2213
  });
1989
- }
1990
- } catch (e) {}
2214
+ } catch {}
2215
+ }
1991
2216
  }
1992
- return stats;
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
+ });
2226
+ }
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 };
1993
2230
  }
1994
2231
 
1995
2232
  // src/cli.ts
1996
- import { writeFileSync, readFileSync as readFileSync2, chmodSync, existsSync as existsSync3 } from "fs";
1997
- import { join as join4 } from "path";
2233
+ import { writeFileSync, readFileSync as readFileSync3, chmodSync, existsSync as existsSync4 } from "fs";
2234
+ import { join as join5 } from "path";
1998
2235
 
1999
2236
  // src/agent.ts
2000
2237
  import { spawnSync as spawnSync2 } from "child_process";
2001
- import { readFileSync, existsSync as existsSync2 } from "fs";
2002
- import { join as join3 } from "path";
2238
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
2239
+ import { join as join4 } from "path";
2003
2240
  import os from "os";
2004
2241
  function detectAgent() {
2005
2242
  const agents = ["claude", "cursor", "aider", "hermes", "cody", "continue", "opencode", "gemini"];
2006
2243
  try {
2007
2244
  const home = os.homedir();
2008
- const historyFiles = [join3(home, ".zsh_history"), join3(home, ".bash_history")];
2245
+ const historyFiles = [join4(home, ".zsh_history"), join4(home, ".bash_history")];
2009
2246
  for (const file of historyFiles) {
2010
- if (existsSync2(file)) {
2011
- const content = readFileSync(file, "utf8");
2247
+ if (existsSync3(file)) {
2248
+ const content = readFileSync2(file, "utf8");
2012
2249
  const lines = content.split(`
2013
2250
  `).filter(Boolean).slice(-500).reverse();
2014
2251
  for (const line of lines) {
@@ -2045,6 +2282,66 @@ var ASCII_LOGO = `
2045
2282
  \\____/|_| |_| |_| |_| |_|___|_____|_| \\_\\
2046
2283
  `;
2047
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
+ }
2048
2345
  async function runOnboarding() {
2049
2346
  console.log(import_picocolors.default.cyan(ASCII_LOGO));
2050
2347
  intro(import_picocolors.default.inverse(" outlier: Welcome "));
@@ -2066,18 +2363,18 @@ As agents write more of our code, we lose visibility into:
2066
2363
  cancel("Onboarding paused. Run outlier again when you are ready.");
2067
2364
  process.exit(0);
2068
2365
  }
2069
- const configPath = join4(os2.homedir(), ".outlier_config");
2366
+ const configPath = join5(os2.homedir(), ".outlier_config");
2070
2367
  writeFileSync(configPath, JSON.stringify({ onboarded: true, date: new Date().toISOString() }));
2071
2368
  }
2072
2369
  async function main() {
2073
2370
  let action = process.argv[2];
2074
2371
  if (action === "daily-greeting") {
2075
- const configPath2 = join4(os2.homedir(), ".outlier_config");
2372
+ const configPath2 = join5(os2.homedir(), ".outlier_config");
2076
2373
  const today = new Date().toISOString().split("T")[0];
2077
2374
  let alreadyRun = false;
2078
- if (existsSync3(configPath2)) {
2375
+ if (existsSync4(configPath2)) {
2079
2376
  try {
2080
- const cfg = JSON.parse(readFileSync2(configPath2, "utf8"));
2377
+ const cfg = JSON.parse(readFileSync3(configPath2, "utf8"));
2081
2378
  if (cfg.lastGreetingDate === today) {
2082
2379
  alreadyRun = true;
2083
2380
  } else {
@@ -2090,6 +2387,10 @@ async function main() {
2090
2387
  process.exit(0);
2091
2388
  action = "status";
2092
2389
  }
2390
+ if (process.argv.includes("--json")) {
2391
+ await emitJson();
2392
+ process.exit(0);
2393
+ }
2093
2394
  console.log(import_picocolors.default.cyan(ASCII_LOGO));
2094
2395
  const pkg = require_package();
2095
2396
  console.log(import_picocolors.default.dim(` Outlier v${pkg.version} · AI Code Reliance & Telemetry Engine
@@ -2104,6 +2405,7 @@ WHAT OUTLIER DOES`));
2104
2405
  console.log(` ${import_picocolors.default.cyan("outlier")} Run the audit (the default — same as 'status')`);
2105
2406
  console.log(` ${import_picocolors.default.cyan("outlier status")} Full audit: who wrote the code, what it cost, your limit`);
2106
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)`);
2107
2409
  console.log(` ${import_picocolors.default.cyan("outlier authorship")} Just the AI-vs-human commit breakdown`);
2108
2410
  console.log(` ${import_picocolors.default.cyan("outlier carbon")} Just the token spend, cache waste & carbon`);
2109
2411
  console.log(` ${import_picocolors.default.cyan("outlier capabilities")} What tools & skills your agents can reach`);
@@ -2118,8 +2420,8 @@ WHAT OUTLIER DOES`));
2118
2420
  console.log(import_picocolors.default.dim("How it works → https://github.com/rosh100yx/outlier#how-it-works"));
2119
2421
  process.exit(0);
2120
2422
  }
2121
- const configPath = join4(os2.homedir(), ".outlier_config");
2122
- if (!existsSync3(configPath) && !action) {
2423
+ const configPath = join5(os2.homedir(), ".outlier_config");
2424
+ if (!existsSync4(configPath) && !action) {
2123
2425
  await runOnboarding();
2124
2426
  action = "status";
2125
2427
  }
@@ -2130,7 +2432,7 @@ WHAT OUTLIER DOES`));
2130
2432
  if (action === "init" || action === "uninit") {
2131
2433
  const shell = process.env.SHELL || "";
2132
2434
  const rcName = shell.includes("zsh") ? ".zshrc" : ".bashrc";
2133
- const rcPath = join4(os2.homedir(), rcName);
2435
+ const rcPath = join5(os2.homedir(), rcName);
2134
2436
  const START_MARKER = "# --- OUTLIER PRE-FLIGHT RITUAL START ---";
2135
2437
  const END_MARKER = "# --- OUTLIER PRE-FLIGHT RITUAL END ---";
2136
2438
  const BLOCK = `
@@ -2150,8 +2452,8 @@ ${END_MARKER}
2150
2452
  process.exit(0);
2151
2453
  }
2152
2454
  let content = "";
2153
- if (existsSync3(rcPath))
2154
- content = readFileSync2(rcPath, "utf8");
2455
+ if (existsSync4(rcPath))
2456
+ content = readFileSync3(rcPath, "utf8");
2155
2457
  if (content.includes(START_MARKER)) {
2156
2458
  note(`Outlier is already initialized in ${rcName}`);
2157
2459
  } else {
@@ -2160,8 +2462,8 @@ ${END_MARKER}
2160
2462
  }
2161
2463
  process.exit(0);
2162
2464
  } else if (action === "uninit") {
2163
- if (existsSync3(rcPath)) {
2164
- let content = readFileSync2(rcPath, "utf8");
2465
+ if (existsSync4(rcPath)) {
2466
+ let content = readFileSync3(rcPath, "utf8");
2165
2467
  if (content.includes(START_MARKER)) {
2166
2468
  const regex = new RegExp(`\\n?${START_MARKER}[\\s\\S]*?${END_MARKER}\\n?`, "g");
2167
2469
  content = content.replace(regex, `
@@ -2230,10 +2532,10 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2230
2532
  let carbon = null;
2231
2533
  let capabilities = null;
2232
2534
  let skipDelay = false;
2233
- const configPath2 = join4(os2.homedir(), ".outlier_config");
2234
- if (existsSync3(configPath2)) {
2535
+ const configPath2 = join5(os2.homedir(), ".outlier_config");
2536
+ if (existsSync4(configPath2)) {
2235
2537
  try {
2236
- const cfg = JSON.parse(readFileSync2(configPath2, "utf8"));
2538
+ const cfg = JSON.parse(readFileSync3(configPath2, "utf8"));
2237
2539
  if (cfg.seenNarration)
2238
2540
  skipDelay = true;
2239
2541
  } catch (e) {}
@@ -2271,7 +2573,7 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2271
2573
  await new Promise((r2) => setTimeout(r2, 600));
2272
2574
  if (!skipDelay) {
2273
2575
  try {
2274
- const cfg = existsSync3(configPath2) ? JSON.parse(readFileSync2(configPath2, "utf8")) : {};
2576
+ const cfg = existsSync4(configPath2) ? JSON.parse(readFileSync3(configPath2, "utf8")) : {};
2275
2577
  cfg.seenNarration = true;
2276
2578
  writeFileSync(configPath2, JSON.stringify(cfg));
2277
2579
  } catch (e) {}
@@ -2299,12 +2601,23 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2299
2601
  let cachePct = "0";
2300
2602
  let co2Str = "0.0kg";
2301
2603
  let regionStr = "Global Average";
2604
+ let sourceLabel = "no local AI logs found";
2605
+ let noData = true;
2302
2606
  if (carbon) {
2303
2607
  if (carbon.totalTokens > 0) {
2304
2608
  cachePct = (carbon.cacheReadTokens / carbon.totalTokens * 100).toFixed(1);
2609
+ noData = false;
2305
2610
  }
2306
2611
  co2Str = `${carbon.localCo2Kg.toFixed(2)}kg CO2`;
2307
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`) : "");
2308
2621
  }
2309
2622
  const isDanger = gitStats && gitStats.ratio > 0.7;
2310
2623
  const verdictZone = isDanger ? import_picocolors.default.red("Mostly AI") : import_picocolors.default.green("You're driving");
@@ -2353,12 +2666,17 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2353
2666
  ${import_picocolors.default.dim("│")} Tokens used ${import_picocolors.default.bold(totalTokensStr)}
2354
2667
  ${import_picocolors.default.dim("│")} Est. spend ${import_picocolors.default.bold(estUsdStr)}
2355
2668
  ${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)`)}
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}`)}
2357
2671
  ${import_picocolors.default.dim("│")}
2358
2672
  ${import_picocolors.default.dim("│")} ${cacheVerdict} — ${cacheText.split(`
2359
2673
  `).join(`
2360
2674
  ` + import_picocolors.default.dim("│") + " ")}
2361
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("├────────────────────────────────────────────────────────")}
2362
2680
  ${import_picocolors.default.dim("│")} ${import_picocolors.default.bold(import_picocolors.default.bgYellow(import_picocolors.default.black(" YOUR LIMIT ")))}
2363
2681
  ${import_picocolors.default.dim("│")} AI cap ${import_picocolors.default.bold("70%")} ${import_picocolors.default.dim("· change with: outlier policy")}
2364
2682
  ${import_picocolors.default.dim("│")} Status ${policyStatus} ${import_picocolors.default.dim("·")} ${policyAction}
@@ -2375,23 +2693,42 @@ Conservative Floor: ${color(nmPct + "%")}`, "Git Authorship Breakdown");
2375
2693
  console.error(import_picocolors.default.red(e.message));
2376
2694
  }
2377
2695
  } else if (action === "capabilities") {
2378
- s.start("Auditing AI surface area (MCPs, Skills, Orchestrators)...");
2696
+ s.start("Mapping what your agents can reach...");
2379
2697
  try {
2380
2698
  const caps = await getCapabilitiesStats();
2381
- s.stop("Capabilities Scan Complete");
2382
- note(`Orchestration Policy: ${caps.hasOrchestration ? import_picocolors.default.green("Detected (AGENTS.md)") : import_picocolors.default.yellow("None")}
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")}
2383
2723
 
2384
- Active Skills (${caps.skills.length}):
2385
- ${caps.skills.length > 0 ? import_picocolors.default.cyan(caps.skills.map((s2) => ` • ${s2}`).join(`
2386
- `)) : " None"}
2724
+ ${import_picocolors.default.bold(`What your agents can reach (${caps.mcps.length} MCP tools):`)}
2725
+ ${toolLines}
2387
2726
 
2388
- Active MCP Servers (${caps.mcps.length}):
2389
- ${caps.mcps.length > 0 ? import_picocolors.default.magenta(caps.mcps.map((m2) => ` • ${m2}`).join(`
2390
- `)) : " None"}
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")}
2391
2730
 
2392
- ${import_picocolors.default.bold("Governance Assessment:")}
2393
- This repository provides agents with ${caps.mcps.length} toolsets and ${caps.skills.length} skills.
2394
- ${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");
2395
2732
  } catch (e) {
2396
2733
  s.stop("Audit failed");
2397
2734
  console.error(import_picocolors.default.red(e.message));
@@ -2425,8 +2762,8 @@ ${caps.skills.length > 5 ? import_picocolors.default.red("⚠ High Surface Area:
2425
2762
  process.exit(0);
2426
2763
  }
2427
2764
  s.start(`Applying ${tier} policy guardrails...`);
2428
- const gitDir = join4(process.cwd(), ".git");
2429
- const isRepo = existsSync3(gitDir);
2765
+ const gitDir = join5(process.cwd(), ".git");
2766
+ const isRepo = existsSync4(gitDir);
2430
2767
  if (!isRepo) {
2431
2768
  console.error(import_picocolors.default.red("Must be run inside a git repository"));
2432
2769
  process.exit(1);
@@ -2434,8 +2771,8 @@ ${caps.skills.length > 5 ? import_picocolors.default.red("⚠ High Surface Area:
2434
2771
  const isStrict = process.argv.includes("--strict");
2435
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%)."
2436
2773
  echo "Take a moment to review your recent architectural decisions. Ensure you still understand the system."`;
2437
- const hookPath = join4(gitDir, "hooks", "pre-commit");
2438
- if (existsSync3(hookPath)) {
2774
+ const hookPath = join5(gitDir, "hooks", "pre-commit");
2775
+ if (existsSync4(hookPath)) {
2439
2776
  const { copyFileSync } = __require("fs");
2440
2777
  copyFileSync(hookPath, `${hookPath}.backup`);
2441
2778
  }
@@ -2468,7 +2805,7 @@ Enforcement: ${import_picocolors.default.cyan("Local pre-commit hook installed
2468
2805
  } else if (tier === "regulatory") {
2469
2806
  s.start("Generating Regulatory Compliance Audit (Decree 142)...");
2470
2807
  await new Promise((resolve) => setTimeout(resolve, 1200));
2471
- const reportPath = join4(process.cwd(), "outlier-audit-report.jsonl");
2808
+ const reportPath = join5(process.cwd(), "outlier-audit-report.jsonl");
2472
2809
  writeFileSync(reportPath, JSON.stringify({ timestamp: new Date().toISOString(), status: "PREVIEW", policy: "Decree 142", simulatedOversight: true }) + `
2473
2810
  `);
2474
2811
  s.stop("Audit Generated");
@@ -2563,7 +2900,7 @@ Read the full academic foundation at: ${import_picocolors.default.underline("htt
2563
2900
  console.log(finalReceipt);
2564
2901
  if (process.argv.includes("--save")) {
2565
2902
  const stripAnsi = (s2) => s2.replace(/\x1b\[[0-9;]*m/g, "");
2566
- const savePath = join4(process.cwd(), "outlier-audit.txt");
2903
+ const savePath = join5(process.cwd(), "outlier-audit.txt");
2567
2904
  try {
2568
2905
  writeFileSync(savePath, stripAnsi(finalReceipt).trimStart() + `
2569
2906
  `);