coderplan-tier 0.1.0 → 0.3.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 +24 -18
- package/dist/collector.js +101 -34
- package/dist/recommender.js +44 -0
- package/dist/renderer.js +42 -8
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,43 +23,49 @@ Developers need guidance on which Claude Code pricing tier best fits their usage
|
|
|
23
23
|
|
|
24
24
|
## How It Works
|
|
25
25
|
|
|
26
|
-
1. Reads
|
|
27
|
-
|
|
26
|
+
1. Reads usage data from both:
|
|
27
|
+
- **Claude Code** via [ccusage](https://github.com/ryoppippi/ccusage)
|
|
28
|
+
- **OpenAI Codex** via [@ccusage/codex](https://www.npmjs.com/package/@ccusage/codex)
|
|
29
|
+
2. Combines and analyzes usage patterns (sessions, tokens, costs, model mix)
|
|
28
30
|
3. Calculates break-even points for each tier
|
|
29
31
|
4. Recommends the tier that minimizes your total cost
|
|
30
32
|
|
|
33
|
+
This is especially useful for developers transitioning from Codex to Claude - see your combined costs and what Claude tier would cover your usage.
|
|
34
|
+
|
|
31
35
|
## Example Output
|
|
32
36
|
|
|
33
37
|
```
|
|
34
|
-
|
|
35
|
-
│
|
|
36
|
-
|
|
38
|
+
╭──────────────────────────────────────────────────────────╮
|
|
39
|
+
│ CoderPlan Tier Recommendation │
|
|
40
|
+
╰──────────────────────────────────────────────────────────╯
|
|
37
41
|
|
|
38
42
|
Your Usage (Last 30 Days)
|
|
39
43
|
Sessions: 47
|
|
40
44
|
Active Days: 22/30
|
|
41
45
|
Total Tokens: 2.4M
|
|
42
|
-
API Cost: $
|
|
43
|
-
|
|
46
|
+
API Cost: $127.32
|
|
47
|
+
├─ Claude: $67.32 (53%)
|
|
48
|
+
└─ Codex: $60.00 (47%)
|
|
49
|
+
Primary Model: Claude Opus (45%), Codex gpt-5 (40%), Claude Sonnet (15%)
|
|
44
50
|
|
|
45
51
|
Recommendation: Max 5x ($100/month)
|
|
46
52
|
Confidence: HIGH (consistent daily usage pattern)
|
|
47
53
|
|
|
48
54
|
Reasoning:
|
|
49
|
-
-
|
|
50
|
-
-
|
|
55
|
+
- Heavy usage (22 active days/month)
|
|
56
|
+
- API costs ($127/mo) exceed Pro break-even (~$25)
|
|
51
57
|
- Max 5x limits would cover your typical usage
|
|
52
58
|
|
|
53
|
-
Projected Savings: ~$
|
|
59
|
+
Projected Savings: ~$65/month vs API-only
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
│ Tier Comparison
|
|
57
|
-
|
|
58
|
-
│ API Only
|
|
59
|
-
│ Pro
|
|
60
|
-
│ Max 5x
|
|
61
|
-
│ Max 20x
|
|
62
|
-
|
|
61
|
+
╭──────────────────────────────────────────────────────────╮
|
|
62
|
+
│ Tier Comparison │
|
|
63
|
+
├──────────────────────────────────────────────────────────┤
|
|
64
|
+
│ API Only $127/month (current spend) │
|
|
65
|
+
│ Pro $58/month ($20 + ~$38 overflow) │
|
|
66
|
+
│ Max 5x $113/month ($100 + ~$13 overflow) <-- │
|
|
67
|
+
│ Max 20x $203/month ($200 + ~$3 overflow) │
|
|
68
|
+
╰──────────────────────────────────────────────────────────╯
|
|
63
69
|
```
|
|
64
70
|
|
|
65
71
|
## CLI Options
|
package/dist/collector.js
CHANGED
|
@@ -1,32 +1,49 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
2
|
export async function getUsageData(days) {
|
|
3
3
|
const since = formatDateYYYYMMDD(new Date(Date.now() - days * 24 * 60 * 60 * 1000));
|
|
4
|
-
//
|
|
5
|
-
const
|
|
6
|
-
|
|
4
|
+
// Fetch both Claude and Codex usage in parallel
|
|
5
|
+
const [claudeData, codexData] = await Promise.all([
|
|
6
|
+
fetchClaudeUsage(since),
|
|
7
|
+
fetchCodexUsage(since),
|
|
8
|
+
]);
|
|
9
|
+
// Aggregate totals
|
|
7
10
|
let totalCost = 0;
|
|
8
11
|
let totalInputTokens = 0;
|
|
9
12
|
let totalOutputTokens = 0;
|
|
10
13
|
let totalCacheCreationTokens = 0;
|
|
11
14
|
let totalCacheReadTokens = 0;
|
|
15
|
+
let claudeCost = 0;
|
|
16
|
+
let codexCost = 0;
|
|
12
17
|
const modelMap = new Map();
|
|
13
|
-
|
|
18
|
+
const dailyMap = new Map();
|
|
19
|
+
// Process Claude data
|
|
20
|
+
for (const day of claudeData.daily) {
|
|
14
21
|
totalCost += day.totalCost;
|
|
22
|
+
claudeCost += day.totalCost;
|
|
15
23
|
totalInputTokens += day.inputTokens;
|
|
16
24
|
totalOutputTokens += day.outputTokens;
|
|
17
|
-
totalCacheCreationTokens += day.cacheCreationTokens;
|
|
18
|
-
totalCacheReadTokens += day.cacheReadTokens;
|
|
25
|
+
totalCacheCreationTokens += day.cacheCreationTokens ?? 0;
|
|
26
|
+
totalCacheReadTokens += day.cacheReadTokens ?? 0;
|
|
27
|
+
// Track daily usage
|
|
28
|
+
const existing = dailyMap.get(day.date);
|
|
29
|
+
if (existing) {
|
|
30
|
+
existing.cost += day.totalCost;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
dailyMap.set(day.date, { date: day.date, cost: day.totalCost, sessions: 1 });
|
|
34
|
+
}
|
|
19
35
|
// Aggregate model usage
|
|
20
|
-
for (const model of day.modelBreakdowns) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
for (const model of day.modelBreakdowns ?? []) {
|
|
37
|
+
const modelName = `Claude ${simplifyModelName(model.modelName)}`;
|
|
38
|
+
const existingModel = modelMap.get(modelName);
|
|
39
|
+
if (existingModel) {
|
|
40
|
+
existingModel.cost += model.cost;
|
|
41
|
+
existingModel.inputTokens += model.inputTokens;
|
|
42
|
+
existingModel.outputTokens += model.outputTokens;
|
|
26
43
|
}
|
|
27
44
|
else {
|
|
28
|
-
modelMap.set(
|
|
29
|
-
model:
|
|
45
|
+
modelMap.set(modelName, {
|
|
46
|
+
model: modelName,
|
|
30
47
|
cost: model.cost,
|
|
31
48
|
inputTokens: model.inputTokens,
|
|
32
49
|
outputTokens: model.outputTokens,
|
|
@@ -35,8 +52,49 @@ export async function getUsageData(days) {
|
|
|
35
52
|
}
|
|
36
53
|
}
|
|
37
54
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
55
|
+
// Process Codex data
|
|
56
|
+
for (const day of codexData.daily) {
|
|
57
|
+
const dayCost = day.costUSD ?? 0;
|
|
58
|
+
totalCost += dayCost;
|
|
59
|
+
codexCost += dayCost;
|
|
60
|
+
totalInputTokens += day.inputTokens;
|
|
61
|
+
totalOutputTokens += day.outputTokens;
|
|
62
|
+
// Track daily usage
|
|
63
|
+
const existing = dailyMap.get(day.date);
|
|
64
|
+
if (existing) {
|
|
65
|
+
existing.cost += dayCost;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
dailyMap.set(day.date, { date: day.date, cost: dayCost, sessions: 1 });
|
|
69
|
+
}
|
|
70
|
+
// Aggregate model usage from models object
|
|
71
|
+
if (day.models) {
|
|
72
|
+
for (const [modelName, modelData] of Object.entries(day.models)) {
|
|
73
|
+
const displayName = `Codex ${modelName}`;
|
|
74
|
+
const existingModel = modelMap.get(displayName);
|
|
75
|
+
// Estimate cost per model proportionally
|
|
76
|
+
const modelTokens = modelData.inputTokens + modelData.outputTokens;
|
|
77
|
+
const dayTotalTokens = day.inputTokens + day.outputTokens;
|
|
78
|
+
const modelCost = dayTotalTokens > 0 ? (modelTokens / dayTotalTokens) * dayCost : 0;
|
|
79
|
+
if (existingModel) {
|
|
80
|
+
existingModel.cost += modelCost;
|
|
81
|
+
existingModel.inputTokens += modelData.inputTokens;
|
|
82
|
+
existingModel.outputTokens += modelData.outputTokens;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
modelMap.set(displayName, {
|
|
86
|
+
model: displayName,
|
|
87
|
+
cost: modelCost,
|
|
88
|
+
inputTokens: modelData.inputTokens,
|
|
89
|
+
outputTokens: modelData.outputTokens,
|
|
90
|
+
percentage: 0,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const activeDays = Array.from(dailyMap.values()).filter((d) => d.cost > 0).length;
|
|
97
|
+
const sessionCount = dailyMap.size;
|
|
40
98
|
// Calculate percentages
|
|
41
99
|
const modelBreakdown = Array.from(modelMap.values()).map((m) => ({
|
|
42
100
|
...m,
|
|
@@ -44,11 +102,7 @@ export async function getUsageData(days) {
|
|
|
44
102
|
}));
|
|
45
103
|
// Sort by cost descending
|
|
46
104
|
modelBreakdown.sort((a, b) => b.cost - a.cost);
|
|
47
|
-
const dailyUsage =
|
|
48
|
-
date: d.date,
|
|
49
|
-
cost: d.totalCost,
|
|
50
|
-
sessions: 1,
|
|
51
|
-
}));
|
|
105
|
+
const dailyUsage = Array.from(dailyMap.values()).sort((a, b) => a.date.localeCompare(b.date));
|
|
52
106
|
return {
|
|
53
107
|
totalCost,
|
|
54
108
|
totalInputTokens,
|
|
@@ -60,37 +114,40 @@ export async function getUsageData(days) {
|
|
|
60
114
|
periodDays: days,
|
|
61
115
|
modelBreakdown,
|
|
62
116
|
dailyUsage,
|
|
117
|
+
claudeCost,
|
|
118
|
+
codexCost,
|
|
63
119
|
};
|
|
64
120
|
}
|
|
65
|
-
function
|
|
121
|
+
async function fetchClaudeUsage(since) {
|
|
122
|
+
return runCommand(`npx ccusage@latest daily --json --since ${since}`, { daily: [] });
|
|
123
|
+
}
|
|
124
|
+
async function fetchCodexUsage(since) {
|
|
125
|
+
return runCommand(`npx @ccusage/codex@latest daily --json --since ${since}`, { daily: [] });
|
|
126
|
+
}
|
|
127
|
+
function runCommand(command, fallback) {
|
|
66
128
|
try {
|
|
67
|
-
const output = execSync(
|
|
129
|
+
const output = execSync(command, {
|
|
68
130
|
encoding: "utf-8",
|
|
69
131
|
stdio: ["pipe", "pipe", "pipe"],
|
|
70
|
-
timeout:
|
|
132
|
+
timeout: 60000,
|
|
71
133
|
});
|
|
72
134
|
return JSON.parse(output);
|
|
73
135
|
}
|
|
74
136
|
catch (error) {
|
|
137
|
+
// Try to parse stdout even on error (some tools exit non-zero with valid output)
|
|
75
138
|
if (error instanceof Error && "stdout" in error) {
|
|
76
139
|
const stdout = error.stdout;
|
|
77
|
-
|
|
78
|
-
if (stdout) {
|
|
140
|
+
if (stdout?.trim()) {
|
|
79
141
|
try {
|
|
80
142
|
return JSON.parse(stdout);
|
|
81
143
|
}
|
|
82
144
|
catch {
|
|
83
|
-
// Fall through
|
|
145
|
+
// Fall through
|
|
84
146
|
}
|
|
85
147
|
}
|
|
86
148
|
}
|
|
87
|
-
// Return
|
|
88
|
-
|
|
89
|
-
(error.message.includes("No usage data") ||
|
|
90
|
-
error.message.includes("ENOENT"))) {
|
|
91
|
-
return { monthly: [], daily: [] };
|
|
92
|
-
}
|
|
93
|
-
throw new Error(`Failed to run ccusage: ${error instanceof Error ? error.message : String(error)}`);
|
|
149
|
+
// Return fallback for missing data
|
|
150
|
+
return fallback;
|
|
94
151
|
}
|
|
95
152
|
}
|
|
96
153
|
function formatDateYYYYMMDD(date) {
|
|
@@ -99,3 +156,13 @@ function formatDateYYYYMMDD(date) {
|
|
|
99
156
|
const day = String(date.getDate()).padStart(2, "0");
|
|
100
157
|
return `${year}${month}${day}`;
|
|
101
158
|
}
|
|
159
|
+
function simplifyModelName(model) {
|
|
160
|
+
const lower = model.toLowerCase();
|
|
161
|
+
if (lower.includes("opus"))
|
|
162
|
+
return "Opus";
|
|
163
|
+
if (lower.includes("sonnet"))
|
|
164
|
+
return "Sonnet";
|
|
165
|
+
if (lower.includes("haiku"))
|
|
166
|
+
return "Haiku";
|
|
167
|
+
return model.split("-").slice(0, 2).join(" ");
|
|
168
|
+
}
|
package/dist/recommender.js
CHANGED
|
@@ -40,6 +40,8 @@ export function recommend(usage) {
|
|
|
40
40
|
const apiOnlyCost = projectedCosts.find((c) => c.tier === "API Only")?.monthlyCost ?? currentMonthlyCost;
|
|
41
41
|
const recommendedCost = projectedCosts.find((c) => c.tier === tier)?.monthlyCost ?? currentMonthlyCost;
|
|
42
42
|
const savings = apiOnlyCost - recommendedCost;
|
|
43
|
+
// Generate explanation comparing recommended tier to next tier up
|
|
44
|
+
const explanation = generateExplanation(tier, projectedCosts);
|
|
43
45
|
return {
|
|
44
46
|
tier,
|
|
45
47
|
confidence,
|
|
@@ -47,6 +49,7 @@ export function recommend(usage) {
|
|
|
47
49
|
projectedCosts,
|
|
48
50
|
currentMonthlyCost,
|
|
49
51
|
savings: Math.max(0, savings),
|
|
52
|
+
explanation,
|
|
50
53
|
};
|
|
51
54
|
}
|
|
52
55
|
function selectLowestCostTier(projectedCosts) {
|
|
@@ -130,3 +133,44 @@ function generateReasons(tier, monthlyCost, activeDays, usage) {
|
|
|
130
133
|
}
|
|
131
134
|
return reasons;
|
|
132
135
|
}
|
|
136
|
+
const TIER_ORDER = ["API Only", "Pro", "Max 5x", "Max 20x"];
|
|
137
|
+
function generateExplanation(recommendedTier, projectedCosts) {
|
|
138
|
+
const recommendedIdx = TIER_ORDER.indexOf(recommendedTier);
|
|
139
|
+
const recommendedCost = projectedCosts.find((c) => c.tier === recommendedTier);
|
|
140
|
+
if (!recommendedCost)
|
|
141
|
+
return null;
|
|
142
|
+
// Compare to next tier up (or down if at max)
|
|
143
|
+
let comparedTier;
|
|
144
|
+
if (recommendedIdx < TIER_ORDER.length - 1) {
|
|
145
|
+
comparedTier = TIER_ORDER[recommendedIdx + 1];
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
comparedTier = TIER_ORDER[recommendedIdx - 1];
|
|
149
|
+
}
|
|
150
|
+
const comparedCost = projectedCosts.find((c) => c.tier === comparedTier);
|
|
151
|
+
if (!comparedCost)
|
|
152
|
+
return null;
|
|
153
|
+
const subscriptionDiff = comparedCost.subscriptionCost - recommendedCost.subscriptionCost;
|
|
154
|
+
const overflowDiff = recommendedCost.overflowCost - comparedCost.overflowCost;
|
|
155
|
+
// Calculate break-even point
|
|
156
|
+
// At break-even: recSub + recOverflow(x) = compSub + compOverflow(x)
|
|
157
|
+
// recSub + x*(1-recCoverage) = compSub + x*(1-compCoverage)
|
|
158
|
+
// x*(compCoverage - recCoverage) = compSub - recSub
|
|
159
|
+
// x = (compSub - recSub) / (compCoverage - recCoverage)
|
|
160
|
+
const recCoverage = COVERAGE_RATIO[recommendedTier];
|
|
161
|
+
const compCoverage = COVERAGE_RATIO[comparedTier];
|
|
162
|
+
const coverageDiff = compCoverage - recCoverage;
|
|
163
|
+
let breakEvenCost = 0;
|
|
164
|
+
if (coverageDiff > 0) {
|
|
165
|
+
breakEvenCost = Math.round(subscriptionDiff / coverageDiff);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
recommendedTier,
|
|
169
|
+
comparedTier,
|
|
170
|
+
recommendedCost,
|
|
171
|
+
comparedCost,
|
|
172
|
+
subscriptionDiff,
|
|
173
|
+
overflowDiff,
|
|
174
|
+
breakEvenCost,
|
|
175
|
+
};
|
|
176
|
+
}
|
package/dist/renderer.js
CHANGED
|
@@ -13,6 +13,14 @@ export function renderRecommendation(usage, rec, verbose) {
|
|
|
13
13
|
lines.push(` Active Days: ${usage.activeDays}/${usage.periodDays}`);
|
|
14
14
|
lines.push(` Total Tokens: ${formatTokens(usage.totalInputTokens + usage.totalOutputTokens)}`);
|
|
15
15
|
lines.push(` API Cost: $${usage.totalCost.toFixed(2)}`);
|
|
16
|
+
// Show Claude/Codex breakdown if both have usage
|
|
17
|
+
if (usage.claudeCost > 0 && usage.codexCost > 0) {
|
|
18
|
+
lines.push(` ${pc.dim("├─")} Claude: $${usage.claudeCost.toFixed(2)} (${((usage.claudeCost / usage.totalCost) * 100).toFixed(0)}%)`);
|
|
19
|
+
lines.push(` ${pc.dim("└─")} Codex: $${usage.codexCost.toFixed(2)} (${((usage.codexCost / usage.totalCost) * 100).toFixed(0)}%)`);
|
|
20
|
+
}
|
|
21
|
+
else if (usage.codexCost > 0) {
|
|
22
|
+
lines.push(` ${pc.dim("└─")} Codex only`);
|
|
23
|
+
}
|
|
16
24
|
// Model breakdown
|
|
17
25
|
if (usage.modelBreakdown.length > 0) {
|
|
18
26
|
const modelStr = usage.modelBreakdown
|
|
@@ -53,6 +61,11 @@ export function renderRecommendation(usage, rec, verbose) {
|
|
|
53
61
|
lines.push(boxContent(line));
|
|
54
62
|
}
|
|
55
63
|
lines.push(boxBottom());
|
|
64
|
+
// Why this tier? explanation
|
|
65
|
+
if (rec.explanation) {
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push(...renderExplanation(rec.explanation));
|
|
68
|
+
}
|
|
56
69
|
// Verbose: show detailed metrics
|
|
57
70
|
if (verbose) {
|
|
58
71
|
lines.push("");
|
|
@@ -76,6 +89,8 @@ export function renderJson(usage, rec) {
|
|
|
76
89
|
activeDays: usage.activeDays,
|
|
77
90
|
sessionCount: usage.sessionCount,
|
|
78
91
|
totalCost: usage.totalCost,
|
|
92
|
+
claudeCost: usage.claudeCost,
|
|
93
|
+
codexCost: usage.codexCost,
|
|
79
94
|
totalTokens: usage.totalInputTokens + usage.totalOutputTokens,
|
|
80
95
|
modelBreakdown: usage.modelBreakdown,
|
|
81
96
|
},
|
|
@@ -86,9 +101,29 @@ export function renderJson(usage, rec) {
|
|
|
86
101
|
currentMonthlyCost: rec.currentMonthlyCost,
|
|
87
102
|
projectedSavings: rec.savings,
|
|
88
103
|
tierCosts: rec.projectedCosts,
|
|
104
|
+
explanation: rec.explanation,
|
|
89
105
|
},
|
|
90
106
|
}, null, 2);
|
|
91
107
|
}
|
|
108
|
+
function renderExplanation(exp) {
|
|
109
|
+
const lines = [];
|
|
110
|
+
const rec = exp.recommendedCost;
|
|
111
|
+
const comp = exp.comparedCost;
|
|
112
|
+
lines.push(pc.bold(`Why ${exp.recommendedTier} over ${exp.comparedTier}?`));
|
|
113
|
+
lines.push(` ${padRight(exp.recommendedTier + ":", 10)} $${rec.subscriptionCost} subscription + $${rec.overflowCost.toFixed(0)} overflow = ${pc.green(`$${rec.monthlyCost.toFixed(0)} total`)}`);
|
|
114
|
+
lines.push(` ${padRight(exp.comparedTier + ":", 10)} $${comp.subscriptionCost} subscription + $${comp.overflowCost.toFixed(0)} overflow = ${pc.dim(`$${comp.monthlyCost.toFixed(0)} total`)}`);
|
|
115
|
+
lines.push("");
|
|
116
|
+
if (exp.subscriptionDiff > 0 && exp.overflowDiff > 0) {
|
|
117
|
+
lines.push(` The $${exp.subscriptionDiff} subscription difference outweighs the $${exp.overflowDiff.toFixed(0)} overflow savings.`);
|
|
118
|
+
}
|
|
119
|
+
else if (exp.subscriptionDiff < 0) {
|
|
120
|
+
lines.push(` Lower subscription ($${Math.abs(exp.subscriptionDiff)} less) with acceptable overflow.`);
|
|
121
|
+
}
|
|
122
|
+
if (exp.breakEvenCost > 0) {
|
|
123
|
+
lines.push(` ${pc.dim(`${exp.comparedTier} becomes better when API costs reach ~$${exp.breakEvenCost}/month.`)}`);
|
|
124
|
+
}
|
|
125
|
+
return lines;
|
|
126
|
+
}
|
|
92
127
|
// Box drawing helpers
|
|
93
128
|
function boxTop() {
|
|
94
129
|
return pc.dim("╭" + "─".repeat(BOX_WIDTH) + "╮");
|
|
@@ -128,14 +163,13 @@ function formatTokens(tokens) {
|
|
|
128
163
|
return tokens.toString();
|
|
129
164
|
}
|
|
130
165
|
function simplifyModelName(model) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return model.split("-").slice(0, 2).join(" ");
|
|
166
|
+
// Model names already have "Claude" or "Codex" prefix from collector
|
|
167
|
+
// Just clean up for display
|
|
168
|
+
return model
|
|
169
|
+
.replace("claude-", "")
|
|
170
|
+
.replace("-20251101", "")
|
|
171
|
+
.replace("-20251001", "")
|
|
172
|
+
.replace("-20250929", "");
|
|
139
173
|
}
|
|
140
174
|
function getTierColor(tier) {
|
|
141
175
|
switch (tier) {
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface UsageData {
|
|
|
9
9
|
periodDays: number;
|
|
10
10
|
modelBreakdown: ModelUsage[];
|
|
11
11
|
dailyUsage: DailyUsage[];
|
|
12
|
+
claudeCost: number;
|
|
13
|
+
codexCost: number;
|
|
12
14
|
}
|
|
13
15
|
export interface ModelUsage {
|
|
14
16
|
model: string;
|
|
@@ -31,6 +33,16 @@ export interface Recommendation {
|
|
|
31
33
|
projectedCosts: TierCost[];
|
|
32
34
|
currentMonthlyCost: number;
|
|
33
35
|
savings: number;
|
|
36
|
+
explanation: TierExplanation | null;
|
|
37
|
+
}
|
|
38
|
+
export interface TierExplanation {
|
|
39
|
+
recommendedTier: Tier;
|
|
40
|
+
comparedTier: Tier;
|
|
41
|
+
recommendedCost: TierCost;
|
|
42
|
+
comparedCost: TierCost;
|
|
43
|
+
subscriptionDiff: number;
|
|
44
|
+
overflowDiff: number;
|
|
45
|
+
breakEvenCost: number;
|
|
34
46
|
}
|
|
35
47
|
export interface TierCost {
|
|
36
48
|
tier: Tier;
|