@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/README.md +18 -1
- package/bin/outlier.js +440 -103
- package/data/grid-factors.json +16 -3
- package/package.json +1 -1
- package/src/capabilities.ts +98 -58
- package/src/carbon.ts +35 -12
- package/src/cli.ts +126 -14
- package/src/emissions.ts +69 -0
- package/src/sources.ts +110 -0
package/src/sources.ts
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Source Detector — the foundation for being tool-agnostic.
|
|
2
|
+
//
|
|
3
|
+
// outlier reads whatever AI telemetry the developer's tools already leave on disk, then
|
|
4
|
+
// uses the richest source per metric and labels its provenance. This keeps us local-first
|
|
5
|
+
// (we never call a tool's API — we read the local trace it writes) and lets us add new
|
|
6
|
+
// tools without changing the receipt.
|
|
7
|
+
//
|
|
8
|
+
// Provenance ladder (per metric): MEASURED > ESTIMATED > PROXY > NONE.
|
|
9
|
+
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
14
|
+
|
|
15
|
+
export type Provenance = 'measured' | 'estimated' | 'proxy' | 'none';
|
|
16
|
+
|
|
17
|
+
export interface DetectedSources {
|
|
18
|
+
tools: string[]; // tools/CLIs found on this machine
|
|
19
|
+
tokenSource: { name: string; provenance: Provenance };
|
|
20
|
+
carbonSource: { name: string; provenance: Provenance };
|
|
21
|
+
capabilitySource: { name: string; provenance: Provenance };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const HOME = homedir();
|
|
25
|
+
|
|
26
|
+
function hasCli(cmd: string): boolean {
|
|
27
|
+
try {
|
|
28
|
+
// `command -v` is POSIX and does not execute the target.
|
|
29
|
+
execSync(`command -v ${cmd}`, { stdio: 'ignore' });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasPath(p: string): boolean {
|
|
37
|
+
try { return existsSync(p); } catch { return false; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Fingerprint the local environment. Cheap checks only (no file reads here).
|
|
41
|
+
export function detectSources(cwd: string = process.cwd()): DetectedSources {
|
|
42
|
+
const tools: string[] = [];
|
|
43
|
+
const add = (t: string) => { if (!tools.includes(t)) tools.push(t); };
|
|
44
|
+
|
|
45
|
+
// AI coding agents (CLI on PATH or a config dir)
|
|
46
|
+
const cliTools: Record<string, string> = {
|
|
47
|
+
claude: 'claude', cursor: 'cursor', aider: 'aider', gemini: 'gemini',
|
|
48
|
+
opencode: 'opencode', cody: 'cody', continue: 'continue', codex: 'codex',
|
|
49
|
+
};
|
|
50
|
+
for (const [name, cmd] of Object.entries(cliTools)) {
|
|
51
|
+
if (hasCli(cmd)) add(name);
|
|
52
|
+
}
|
|
53
|
+
for (const [name, dir] of Object.entries({
|
|
54
|
+
claude: '.claude', cursor: '.cursor', gemini: '.gemini',
|
|
55
|
+
codeium: '.codeium', continue: '.continue', aider: '.aider.conf.yml',
|
|
56
|
+
})) {
|
|
57
|
+
if (hasPath(join(HOME, dir))) add(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Carbon/cost tooling that writes local data we can trust
|
|
61
|
+
if (hasCli('codecarbon')) add('codecarbon');
|
|
62
|
+
if (hasCli('ccusage')) add('ccusage');
|
|
63
|
+
|
|
64
|
+
// ---- Token / cost source (richest first) ----
|
|
65
|
+
const slug = cwd.replace(/\//g, '-');
|
|
66
|
+
const claudeProjectDir = join(HOME, '.claude', 'projects', slug);
|
|
67
|
+
const tokenomicsLog = join(HOME, '.claude', 'tokenomics-log.jsonl');
|
|
68
|
+
let tokenSource: DetectedSources['tokenSource'];
|
|
69
|
+
if (hasPath(tokenomicsLog)) {
|
|
70
|
+
// Custom Stop hook: carries a real cost_usd field -> measured cost.
|
|
71
|
+
tokenSource = { name: 'caveman tokenomics log', provenance: 'measured' };
|
|
72
|
+
} else if (hasPath(claudeProjectDir)) {
|
|
73
|
+
// Standard transcripts: real tokens, estimated cost.
|
|
74
|
+
tokenSource = { name: 'Claude Code transcripts', provenance: 'estimated' };
|
|
75
|
+
} else if (tools.includes('ccusage')) {
|
|
76
|
+
tokenSource = { name: 'ccusage', provenance: 'estimated' };
|
|
77
|
+
} else {
|
|
78
|
+
tokenSource = { name: 'none', provenance: 'none' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- Carbon source ----
|
|
82
|
+
// Baseline is our bundled offline model+grid ESTIMATE. CodeCarbon, when it has actually
|
|
83
|
+
// written an emissions.csv, is a higher-accuracy MEASURED path (parser wired in a later
|
|
84
|
+
// pass). We do not claim "measured" just because the CLI is installed.
|
|
85
|
+
let carbonSource: DetectedSources['carbonSource'];
|
|
86
|
+
const codecarbonData = hasPath(join(cwd, 'emissions.csv')) || hasPath(join(HOME, '.codecarbon', 'emissions.csv'));
|
|
87
|
+
if (codecarbonData) {
|
|
88
|
+
carbonSource = { name: 'CodeCarbon emissions.csv', provenance: 'measured' };
|
|
89
|
+
} else if (tokenSource.provenance !== 'none') {
|
|
90
|
+
carbonSource = { name: 'model+grid estimate', provenance: 'estimated' };
|
|
91
|
+
} else {
|
|
92
|
+
carbonSource = { name: 'none', provenance: 'none' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---- Capability source ----
|
|
96
|
+
let capabilitySource: DetectedSources['capabilitySource'];
|
|
97
|
+
if (hasPath(join(HOME, '.claude', 'settings.json')) || hasPath(join(cwd, 'AGENTS.md')) || hasPath(join(cwd, '.mcp.json'))) {
|
|
98
|
+
capabilitySource = { name: 'local config (settings/AGENTS/MCP)', provenance: 'measured' };
|
|
99
|
+
} else {
|
|
100
|
+
capabilitySource = { name: 'none', provenance: 'none' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { tools, tokenSource, carbonSource, capabilitySource };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Short label for the receipt, e.g. "measured · caveman tokenomics log".
|
|
107
|
+
export function provLabel(s: { name: string; provenance: Provenance }): string {
|
|
108
|
+
if (s.provenance === 'none') return 'no local data';
|
|
109
|
+
return `${s.provenance} · ${s.name}`;
|
|
110
|
+
}
|