codeslop 0.1.0 → 0.1.2
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/wasted.js +25 -8
- package/lib/cost.js +1 -2
- package/lib/store.js +1 -1
- package/package.json +2 -2
- package/shared/aliases.js +71 -0
- package/shared/pricing.js +92 -0
- package/shared/severity.js +8 -0
- package/shared/token-profiles.js +15 -0
package/bin/wasted.js
CHANGED
|
@@ -65,24 +65,41 @@ async function init() {
|
|
|
65
65
|
console.log(` stream: ${config.stream} country: ${config.country || 'not set'}\n`);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
console.log(`\n ${R}${B}codeslop${X} ${D}— tracking every dollar your AI agent lights on fire${X}\n`);
|
|
70
|
+
|
|
71
|
+
let nickname = args[1];
|
|
72
|
+
if (!nickname) {
|
|
73
|
+
const rl = await import('readline');
|
|
74
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
75
|
+
const ask = q => new Promise(r => iface.question(q, a => { r(a.trim()); }));
|
|
76
|
+
nickname = await ask(` ${B}What should we call you?${X} `) || process.env.USER || 'anon';
|
|
77
|
+
iface.close();
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
const user_id = randomUUID().slice(0, 12);
|
|
69
|
-
const nickname = args[1] || process.env.USER || 'anon';
|
|
70
81
|
config.user_id = user_id;
|
|
71
82
|
config.nickname = nickname;
|
|
72
83
|
saveConfig(config);
|
|
84
|
+
|
|
85
|
+
console.log(`\n ${G}${B}✓ Welcome, ${nickname}!${X} ${D}(${user_id})${X}\n`);
|
|
86
|
+
|
|
73
87
|
try {
|
|
74
88
|
await fetch(config.api_url+'/api/users',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({user_id,nickname})});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
${
|
|
89
|
+
console.log(` ${G}✓${X} registered with codeslop.org`);
|
|
90
|
+
} catch {
|
|
91
|
+
console.log(` ${D}(offline — will sync later)${X}`);
|
|
92
|
+
}
|
|
78
93
|
|
|
79
|
-
|
|
94
|
+
console.log(`
|
|
95
|
+
${D}stream mode:${X} ${Y}daily${X} ${D}(only aggregated summaries leave your machine)${X}
|
|
80
96
|
${D}local db:${X} ~/.wasted/wasted.db
|
|
81
97
|
${D}config:${X} ~/.wasted/config.json
|
|
82
98
|
|
|
83
|
-
${
|
|
84
|
-
${G}codeslop
|
|
85
|
-
${G}codeslop
|
|
99
|
+
${B}next steps:${X}
|
|
100
|
+
${G}codeslop scan${X} → detect your AI clients
|
|
101
|
+
${G}codeslop log${X} → record waste manually
|
|
102
|
+
${D}or type ${G}/slop${D} in Claude Code to auto-detect waste${X}
|
|
86
103
|
`);
|
|
87
104
|
}
|
|
88
105
|
|
package/lib/cost.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { wasteCost } from '../../shared/pricing.js';
|
|
1
|
+
export { wasteCost } from '../shared/pricing.js';
|
package/lib/store.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdirSync } from 'fs';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
|
-
import { wasteCost } from '
|
|
6
|
+
import { wasteCost } from '../shared/pricing.js';
|
|
7
7
|
|
|
8
8
|
const DIR = join(homedir(), '.wasted');
|
|
9
9
|
const DB_PATH = join(DIR, 'wasted.db');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeslop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Track every dollar your AI agent lights on fire. Works with Claude Code, Cursor, Windsurf, Copilot, Aider, OpenCode. codeslop.org",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codeslop": "./bin/wasted.js"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"keywords": ["ai", "tokens", "waste", "slop", "codeslop", "llm", "claude", "cursor", "copilot", "windsurf", "opencode", "aider"],
|
|
10
10
|
"license": "MIT",
|
|
11
|
-
"files": ["bin/", "lib/"],
|
|
11
|
+
"files": ["bin/", "lib/", "shared/"],
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"better-sqlite3": "^11.6.0"
|
|
14
14
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Model name resolution: normalize user input to canonical model IDs.
|
|
2
|
+
|
|
3
|
+
const MODEL_ALIASES = {
|
|
4
|
+
// Anthropic short names
|
|
5
|
+
'opus': 'claude-opus-4-6',
|
|
6
|
+
'claude-opus': 'claude-opus-4-6',
|
|
7
|
+
'claude-opus-4': 'claude-opus-4-6',
|
|
8
|
+
'sonnet': 'claude-sonnet-4-6',
|
|
9
|
+
'claude-sonnet': 'claude-sonnet-4-6',
|
|
10
|
+
'claude-sonnet-4': 'claude-sonnet-4-6',
|
|
11
|
+
'claude-sonnet-4-5': 'claude-sonnet-4-5',
|
|
12
|
+
'haiku': 'claude-haiku-4-5',
|
|
13
|
+
'claude-haiku': 'claude-haiku-4-5',
|
|
14
|
+
'claude-haiku-3-5': 'claude-haiku-3-5',
|
|
15
|
+
|
|
16
|
+
// OpenAI short names
|
|
17
|
+
'gpt4o': 'gpt-4o',
|
|
18
|
+
'gpt4': 'gpt-4',
|
|
19
|
+
'4o': 'gpt-4o',
|
|
20
|
+
'4o-mini': 'gpt-4o-mini',
|
|
21
|
+
'gpt-4o-mini': 'gpt-4o-mini',
|
|
22
|
+
'gpt-3.5': 'gpt-3.5-turbo',
|
|
23
|
+
'gpt-3.5-turbo': 'gpt-3.5-turbo',
|
|
24
|
+
|
|
25
|
+
// Google short names
|
|
26
|
+
'gemini-pro': 'gemini-2.5-pro',
|
|
27
|
+
'gemini-flash': 'gemini-2.5-flash',
|
|
28
|
+
'gemini': 'gemini-2.5-pro',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const PROVIDER_PREFIXES = ['anthropic/', 'openai/', 'google/', 'gemini/', 'vertex_ai/'];
|
|
32
|
+
|
|
33
|
+
const CANONICAL_IDS = new Set([
|
|
34
|
+
'claude-opus-4-6', 'claude-opus-4', 'claude-sonnet-4-6', 'claude-sonnet-4-5',
|
|
35
|
+
'claude-sonnet-4', 'claude-haiku-4-5', 'claude-haiku-3-5',
|
|
36
|
+
'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo',
|
|
37
|
+
'o1', 'o1-mini', 'o3', 'o3-mini',
|
|
38
|
+
'gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-2.5-flash-lite',
|
|
39
|
+
'gemini-3-flash', 'gemini-3.1-pro',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a model name to its canonical ID.
|
|
44
|
+
* Handles aliases, provider prefixes, and case insensitivity.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveModel(input) {
|
|
47
|
+
if (!input) return 'unknown';
|
|
48
|
+
|
|
49
|
+
let key = input.toLowerCase().trim();
|
|
50
|
+
|
|
51
|
+
// Strip provider prefixes
|
|
52
|
+
for (const prefix of PROVIDER_PREFIXES) {
|
|
53
|
+
if (key.startsWith(prefix)) {
|
|
54
|
+
key = key.slice(prefix.length);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Exact alias match
|
|
60
|
+
if (MODEL_ALIASES[key]) return MODEL_ALIASES[key];
|
|
61
|
+
|
|
62
|
+
// Already a canonical ID
|
|
63
|
+
if (CANONICAL_IDS.has(key)) return key;
|
|
64
|
+
|
|
65
|
+
// Substring match as last resort
|
|
66
|
+
for (const id of CANONICAL_IDS) {
|
|
67
|
+
if (id.includes(key) || key.includes(id)) return id;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return 'unknown';
|
|
71
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// PRICING — single source of truth for all model token costs
|
|
3
|
+
// ============================================================================
|
|
4
|
+
//
|
|
5
|
+
// All prices in USD per 1 token (NOT per 1M tokens).
|
|
6
|
+
// Update this table when providers change prices — it's rare.
|
|
7
|
+
//
|
|
8
|
+
// Sources:
|
|
9
|
+
// Anthropic: https://platform.claude.com/docs/en/about-claude/pricing
|
|
10
|
+
// OpenAI: https://openai.com/api/pricing/
|
|
11
|
+
// Google: https://ai.google.dev/gemini-api/docs/pricing
|
|
12
|
+
//
|
|
13
|
+
// Last updated: 2026-03-18
|
|
14
|
+
//
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
import { resolveModel } from './aliases.js';
|
|
18
|
+
import { CATEGORY_TOKEN_PROFILE } from './token-profiles.js';
|
|
19
|
+
|
|
20
|
+
export const PRICING = {
|
|
21
|
+
// --- Anthropic Claude ---
|
|
22
|
+
'claude-opus-4-6': { input: 5.00 / 1e6, output: 25.00 / 1e6 },
|
|
23
|
+
'claude-opus-4': { input: 5.00 / 1e6, output: 25.00 / 1e6 },
|
|
24
|
+
'claude-sonnet-4-6': { input: 3.00 / 1e6, output: 15.00 / 1e6 },
|
|
25
|
+
'claude-sonnet-4-5': { input: 3.00 / 1e6, output: 15.00 / 1e6 },
|
|
26
|
+
'claude-sonnet-4': { input: 3.00 / 1e6, output: 15.00 / 1e6 },
|
|
27
|
+
'claude-haiku-4-5': { input: 1.00 / 1e6, output: 5.00 / 1e6 },
|
|
28
|
+
'claude-haiku-3-5': { input: 0.80 / 1e6, output: 4.00 / 1e6 },
|
|
29
|
+
|
|
30
|
+
// --- OpenAI ---
|
|
31
|
+
'gpt-4o': { input: 2.50 / 1e6, output: 10.00 / 1e6 },
|
|
32
|
+
'gpt-4o-mini': { input: 0.15 / 1e6, output: 0.60 / 1e6 },
|
|
33
|
+
'gpt-4-turbo': { input: 10.0 / 1e6, output: 30.00 / 1e6 },
|
|
34
|
+
'gpt-4': { input: 30.0 / 1e6, output: 60.00 / 1e6 },
|
|
35
|
+
'gpt-3.5-turbo': { input: 0.50 / 1e6, output: 1.50 / 1e6 },
|
|
36
|
+
'o1': { input: 15.0 / 1e6, output: 60.00 / 1e6 },
|
|
37
|
+
'o1-mini': { input: 1.10 / 1e6, output: 4.40 / 1e6 },
|
|
38
|
+
'o3': { input: 10.0 / 1e6, output: 40.00 / 1e6 },
|
|
39
|
+
'o3-mini': { input: 1.10 / 1e6, output: 4.40 / 1e6 },
|
|
40
|
+
|
|
41
|
+
// --- Google Gemini ---
|
|
42
|
+
'gemini-2.5-pro': { input: 1.25 / 1e6, output: 10.00 / 1e6 },
|
|
43
|
+
'gemini-2.5-flash': { input: 0.30 / 1e6, output: 2.50 / 1e6 },
|
|
44
|
+
'gemini-2.5-flash-lite': { input: 0.10 / 1e6, output: 0.40 / 1e6 },
|
|
45
|
+
'gemini-3-flash': { input: 0.50 / 1e6, output: 3.00 / 1e6 },
|
|
46
|
+
'gemini-3.1-pro': { input: 2.00 / 1e6, output: 12.00 / 1e6 },
|
|
47
|
+
|
|
48
|
+
// --- Fallback ---
|
|
49
|
+
'unknown': { input: 2.50 / 1e6, output: 10.00 / 1e6 },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get price rates for a model. Resolves aliases automatically.
|
|
54
|
+
*/
|
|
55
|
+
export function getPrice(model) {
|
|
56
|
+
const id = resolveModel(model);
|
|
57
|
+
return PRICING[id] || PRICING['unknown'];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate cost for known input/output token counts.
|
|
62
|
+
*/
|
|
63
|
+
export function calculateCost(model, inputTokens, outputTokens) {
|
|
64
|
+
const price = getPrice(model);
|
|
65
|
+
const inputCost = inputTokens * price.input;
|
|
66
|
+
const outputCost = outputTokens * price.output;
|
|
67
|
+
return {
|
|
68
|
+
cost: inputCost + outputCost,
|
|
69
|
+
breakdown: { input: inputCost, output: outputCost },
|
|
70
|
+
rates: price,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Estimate cost when input/output split is unknown (40/60 heuristic).
|
|
76
|
+
*/
|
|
77
|
+
export function estimateCost(model, totalTokens) {
|
|
78
|
+
const inputTokens = Math.round(totalTokens * 0.4);
|
|
79
|
+
const outputTokens = totalTokens - inputTokens;
|
|
80
|
+
return calculateCost(model, inputTokens, outputTokens);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Calculate waste cost using model pricing + category token profile.
|
|
85
|
+
*/
|
|
86
|
+
export function wasteCost(model, category, tokens) {
|
|
87
|
+
const price = getPrice(model);
|
|
88
|
+
const prof = CATEGORY_TOKEN_PROFILE[category] || { inputRatio: 0.4, outputRatio: 0.6 };
|
|
89
|
+
const inp = Math.round(tokens * prof.inputRatio);
|
|
90
|
+
const out = tokens - inp;
|
|
91
|
+
return inp * price.input + out * price.output;
|
|
92
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Severity multipliers reduce waste token count for lower-confidence detections.
|
|
2
|
+
// A "mild" detection counts only 30% of tokens as waste, reducing false-positive inflation.
|
|
3
|
+
|
|
4
|
+
export const SEVERITY_MULTIPLIER = {
|
|
5
|
+
severe: 1.0,
|
|
6
|
+
moderate: 0.7,
|
|
7
|
+
mild: 0.3,
|
|
8
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Category → input/output token split for waste cost calculation.
|
|
2
|
+
// Yapping = mostly output tokens (expensive). Context stuffing = mostly input (cheaper per token).
|
|
3
|
+
|
|
4
|
+
export const CATEGORY_TOKEN_PROFILE = {
|
|
5
|
+
yapping: { inputRatio: 0.1, outputRatio: 0.9 },
|
|
6
|
+
groundhog_day: { inputRatio: 0.5, outputRatio: 0.5 },
|
|
7
|
+
ghost_agent: { inputRatio: 0.4, outputRatio: 0.6 },
|
|
8
|
+
prompt_salad: { inputRatio: 0.3, outputRatio: 0.7 },
|
|
9
|
+
tool_spam: { inputRatio: 0.7, outputRatio: 0.3 },
|
|
10
|
+
context_stuffing: { inputRatio: 0.95, outputRatio: 0.05 },
|
|
11
|
+
hallucination_trip: { inputRatio: 0.1, outputRatio: 0.9 },
|
|
12
|
+
ping_pong: { inputRatio: 0.5, outputRatio: 0.5 },
|
|
13
|
+
skill_issue: { inputRatio: 0.4, outputRatio: 0.6 },
|
|
14
|
+
overthink: { inputRatio: 0.1, outputRatio: 0.9 },
|
|
15
|
+
};
|