coderplan-tier 0.1.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 ADDED
@@ -0,0 +1,108 @@
1
+ # coderplan-tier
2
+
3
+ Analyze your Claude Code usage and get a recommendation for the optimal pricing tier.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx coderplan-tier@latest
9
+ ```
10
+
11
+ ## Problem
12
+
13
+ Developers need guidance on which Claude Code pricing tier best fits their usage patterns. This tool analyzes actual usage data and recommends the most cost-effective plan.
14
+
15
+ ## Tier Overview
16
+
17
+ | Tier | Monthly Cost | Rate Limits | Best For |
18
+ |------|--------------|-------------|----------|
19
+ | API Only | Pay-per-token | Unlimited | Occasional users (<1 session/week) |
20
+ | Pro | $20/month | ~45 msgs/5hr, 40-80 hrs/week | Regular daily users |
21
+ | Max 5x | $100/month | 5× Pro limits | Heavy users, multiple projects |
22
+ | Max 20x | $200/month | 20× Pro limits | Power users, constant usage |
23
+
24
+ ## How It Works
25
+
26
+ 1. Reads your Claude Code usage data via [ccusage](https://github.com/ryoppippi/ccusage)
27
+ 2. Analyzes usage patterns (sessions, tokens, costs, model mix)
28
+ 3. Calculates break-even points for each tier
29
+ 4. Recommends the tier that minimizes your total cost
30
+
31
+ ## Example Output
32
+
33
+ ```
34
+ ╭─────────────────────────────────────────────────────────╮
35
+ │ CoderPlan Tier Recommendation │
36
+ ╰─────────────────────────────────────────────────────────╯
37
+
38
+ Your Usage (Last 30 Days)
39
+ Sessions: 47
40
+ Active Days: 22/30
41
+ Total Tokens: 2.4M
42
+ API Cost: $67.32
43
+ Primary Model: Claude Sonnet (82%), Opus (18%)
44
+
45
+ Recommendation: Max 5x ($100/month)
46
+ Confidence: HIGH (consistent daily usage pattern)
47
+
48
+ Reasoning:
49
+ - You use Claude Code 22 days/month (heavy usage)
50
+ - Current API costs ($67/mo) exceed Pro break-even (~$30)
51
+ - Max 5x limits would cover your typical usage
52
+
53
+ Projected Savings: ~$47/month vs API-only
54
+
55
+ ╭─────────────────────────────────────────────────────────╮
56
+ │ Tier Comparison │
57
+ ├─────────────────────────────────────────────────────────┤
58
+ │ API Only $67/month (current spend) │
59
+ │ Pro $35/month ($20 + ~$15 overflow) │
60
+ │ Max 5x $100/month (no overflow expected) <-- │
61
+ │ Max 20x $200/month (overkill) │
62
+ ╰─────────────────────────────────────────────────────────╯
63
+ ```
64
+
65
+ ## CLI Options
66
+
67
+ ```bash
68
+ coderplan-tier # Analyze last 30 days (default)
69
+ coderplan-tier --period 90 # Analyze last 90 days
70
+ coderplan-tier --json # Output as JSON
71
+ coderplan-tier --verbose # Show detailed metrics
72
+ ```
73
+
74
+ ## Break-Even Analysis
75
+
76
+ The recommendation algorithm considers:
77
+
78
+ | Tier | Break-Even Point |
79
+ |------|------------------|
80
+ | Pro ($20) | ~$25-30/month API spend |
81
+ | Max 5x ($100) | ~$120-150/month API spend |
82
+ | Max 20x ($200) | ~$250+/month API spend |
83
+
84
+ ## Confidence Levels
85
+
86
+ The tool shows a confidence indicator based on usage variance:
87
+
88
+ - **HIGH**: Consistent daily usage, stable patterns
89
+ - **MEDIUM**: Some variation but clear trend
90
+ - **LOW**: Highly variable (heavy some weeks, idle others)
91
+
92
+ ## API Token Pricing Reference
93
+
94
+ | Model | Input (per 1M) | Output (per 1M) |
95
+ |-------|----------------|-----------------|
96
+ | Haiku | $1 | $5 |
97
+ | Sonnet 4.5 | $3 | $15 |
98
+ | Opus 4.5 | $5 | $25 |
99
+ | Opus 4.1 | $15 | $75 |
100
+
101
+ ## Requirements
102
+
103
+ - Node.js 18+
104
+ - Claude Code usage history (stored locally by Claude Code)
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,2 @@
1
+ import type { UsageData } from "./types.js";
2
+ export declare function getUsageData(days: number): Promise<UsageData>;
@@ -0,0 +1,101 @@
1
+ import { execSync } from "child_process";
2
+ export async function getUsageData(days) {
3
+ const since = formatDateYYYYMMDD(new Date(Date.now() - days * 24 * 60 * 60 * 1000));
4
+ // Get daily data (more granular for active days calculation)
5
+ const dailyData = runCcusage("daily", since);
6
+ // Aggregate totals from daily data
7
+ let totalCost = 0;
8
+ let totalInputTokens = 0;
9
+ let totalOutputTokens = 0;
10
+ let totalCacheCreationTokens = 0;
11
+ let totalCacheReadTokens = 0;
12
+ const modelMap = new Map();
13
+ for (const day of dailyData.daily) {
14
+ totalCost += day.totalCost;
15
+ totalInputTokens += day.inputTokens;
16
+ totalOutputTokens += day.outputTokens;
17
+ totalCacheCreationTokens += day.cacheCreationTokens;
18
+ totalCacheReadTokens += day.cacheReadTokens;
19
+ // Aggregate model usage
20
+ for (const model of day.modelBreakdowns) {
21
+ const existing = modelMap.get(model.modelName);
22
+ if (existing) {
23
+ existing.cost += model.cost;
24
+ existing.inputTokens += model.inputTokens;
25
+ existing.outputTokens += model.outputTokens;
26
+ }
27
+ else {
28
+ modelMap.set(model.modelName, {
29
+ model: model.modelName,
30
+ cost: model.cost,
31
+ inputTokens: model.inputTokens,
32
+ outputTokens: model.outputTokens,
33
+ percentage: 0,
34
+ });
35
+ }
36
+ }
37
+ }
38
+ const activeDays = dailyData.daily.filter((d) => d.totalCost > 0).length;
39
+ const sessionCount = dailyData.daily.length; // Approximation: 1 session per active day
40
+ // Calculate percentages
41
+ const modelBreakdown = Array.from(modelMap.values()).map((m) => ({
42
+ ...m,
43
+ percentage: totalCost > 0 ? (m.cost / totalCost) * 100 : 0,
44
+ }));
45
+ // Sort by cost descending
46
+ modelBreakdown.sort((a, b) => b.cost - a.cost);
47
+ const dailyUsage = dailyData.daily.map((d) => ({
48
+ date: d.date,
49
+ cost: d.totalCost,
50
+ sessions: 1,
51
+ }));
52
+ return {
53
+ totalCost,
54
+ totalInputTokens,
55
+ totalOutputTokens,
56
+ totalCacheCreationTokens,
57
+ totalCacheReadTokens,
58
+ sessionCount,
59
+ activeDays,
60
+ periodDays: days,
61
+ modelBreakdown,
62
+ dailyUsage,
63
+ };
64
+ }
65
+ function runCcusage(command, since) {
66
+ try {
67
+ const output = execSync(`npx ccusage@latest ${command} --json --since ${since}`, {
68
+ encoding: "utf-8",
69
+ stdio: ["pipe", "pipe", "pipe"],
70
+ timeout: 30000,
71
+ });
72
+ return JSON.parse(output);
73
+ }
74
+ catch (error) {
75
+ if (error instanceof Error && "stdout" in error) {
76
+ const stdout = error.stdout;
77
+ // ccusage may return empty data with exit code 0
78
+ if (stdout) {
79
+ try {
80
+ return JSON.parse(stdout);
81
+ }
82
+ catch {
83
+ // Fall through to error handling
84
+ }
85
+ }
86
+ }
87
+ // Return empty data structure for no data case
88
+ if (error instanceof Error &&
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)}`);
94
+ }
95
+ }
96
+ function formatDateYYYYMMDD(date) {
97
+ const year = date.getFullYear();
98
+ const month = String(date.getMonth() + 1).padStart(2, "0");
99
+ const day = String(date.getDate()).padStart(2, "0");
100
+ return `${year}${month}${day}`;
101
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import { getUsageData } from "./collector.js";
4
+ import { recommend } from "./recommender.js";
5
+ import { renderRecommendation, renderJson } from "./renderer.js";
6
+ program
7
+ .name("coderplan-tier")
8
+ .description("Analyze Claude Code usage and get tier recommendations")
9
+ .version("0.1.0")
10
+ .option("-p, --period <days>", "Analysis period in days", "30")
11
+ .option("-j, --json", "Output as JSON")
12
+ .option("-v, --verbose", "Show detailed metrics")
13
+ .action(async (options) => {
14
+ const days = parseInt(options.period, 10);
15
+ if (isNaN(days) || days < 1 || days > 365) {
16
+ console.error("Error: Period must be between 1 and 365 days");
17
+ process.exit(1);
18
+ }
19
+ try {
20
+ const usage = await getUsageData(days);
21
+ if (usage.totalCost === 0 && usage.sessionCount === 0) {
22
+ console.log("No Claude Code usage data found for the specified period.");
23
+ console.log("Make sure you have used Claude Code and ccusage can access your data.");
24
+ process.exit(0);
25
+ }
26
+ const recommendation = recommend(usage);
27
+ if (options.json) {
28
+ console.log(renderJson(usage, recommendation));
29
+ }
30
+ else {
31
+ console.log(renderRecommendation(usage, recommendation, options.verbose));
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.error("Error:", error instanceof Error ? error.message : String(error));
36
+ process.exit(1);
37
+ }
38
+ });
39
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import type { UsageData, Recommendation } from "./types.js";
2
+ export declare function recommend(usage: UsageData): Recommendation;
@@ -0,0 +1,132 @@
1
+ // Tier subscription costs
2
+ const TIER_COSTS = {
3
+ "API Only": 0,
4
+ Pro: 20,
5
+ "Max 5x": 100,
6
+ "Max 20x": 200,
7
+ };
8
+ // Approximate break-even points (API cost at which tier becomes worthwhile)
9
+ const BREAK_EVEN = {
10
+ "API Only": 0,
11
+ Pro: 25,
12
+ "Max 5x": 120,
13
+ "Max 20x": 250,
14
+ };
15
+ // Approximate coverage ratios (what % of usage is covered by subscription)
16
+ // These are estimates based on typical usage patterns
17
+ const COVERAGE_RATIO = {
18
+ "API Only": 0,
19
+ Pro: 0.7, // Pro covers ~70% of moderate usage
20
+ "Max 5x": 0.9, // Max 5x covers ~90% of heavy usage
21
+ "Max 20x": 0.98, // Max 20x covers nearly all usage
22
+ };
23
+ export function recommend(usage) {
24
+ const monthlyMultiplier = 30 / usage.periodDays;
25
+ const currentMonthlyCost = usage.totalCost * monthlyMultiplier;
26
+ const activeDaysPerMonth = usage.activeDays * monthlyMultiplier;
27
+ // Calculate projected costs for each tier
28
+ const projectedCosts = calculateTierCosts(currentMonthlyCost);
29
+ // Select tier with lowest total cost
30
+ const tier = selectLowestCostTier(projectedCosts);
31
+ // Calculate confidence
32
+ const confidence = calculateConfidence(usage);
33
+ // Generate reasons
34
+ const reasons = generateReasons(tier, currentMonthlyCost, activeDaysPerMonth, usage);
35
+ // Mark recommended tier
36
+ for (const cost of projectedCosts) {
37
+ cost.isRecommended = cost.tier === tier;
38
+ }
39
+ // Calculate savings vs API-only
40
+ const apiOnlyCost = projectedCosts.find((c) => c.tier === "API Only")?.monthlyCost ?? currentMonthlyCost;
41
+ const recommendedCost = projectedCosts.find((c) => c.tier === tier)?.monthlyCost ?? currentMonthlyCost;
42
+ const savings = apiOnlyCost - recommendedCost;
43
+ return {
44
+ tier,
45
+ confidence,
46
+ reasons,
47
+ projectedCosts,
48
+ currentMonthlyCost,
49
+ savings: Math.max(0, savings),
50
+ };
51
+ }
52
+ function selectLowestCostTier(projectedCosts) {
53
+ // Find tier with minimum total cost
54
+ let minCost = Infinity;
55
+ let bestTier = "API Only";
56
+ for (const cost of projectedCosts) {
57
+ if (cost.monthlyCost < minCost) {
58
+ minCost = cost.monthlyCost;
59
+ bestTier = cost.tier;
60
+ }
61
+ }
62
+ return bestTier;
63
+ }
64
+ function calculateTierCosts(currentMonthlyCost) {
65
+ return ["API Only", "Pro", "Max 5x", "Max 20x"].map((tier) => {
66
+ const subscriptionCost = TIER_COSTS[tier];
67
+ const coverage = COVERAGE_RATIO[tier];
68
+ // Overflow = portion of usage not covered by subscription
69
+ const overflowCost = tier === "API Only"
70
+ ? currentMonthlyCost
71
+ : Math.max(0, currentMonthlyCost * (1 - coverage));
72
+ return {
73
+ tier,
74
+ subscriptionCost,
75
+ overflowCost: Math.round(overflowCost * 100) / 100,
76
+ monthlyCost: Math.round((subscriptionCost + overflowCost) * 100) / 100,
77
+ isRecommended: false,
78
+ };
79
+ });
80
+ }
81
+ function calculateConfidence(usage) {
82
+ if (usage.dailyUsage.length < 7) {
83
+ return "LOW"; // Not enough data
84
+ }
85
+ // Calculate coefficient of variation for daily costs
86
+ const costs = usage.dailyUsage.map((d) => d.cost);
87
+ const mean = costs.reduce((a, b) => a + b, 0) / costs.length;
88
+ if (mean === 0) {
89
+ return "LOW";
90
+ }
91
+ const variance = costs.reduce((sum, c) => sum + Math.pow(c - mean, 2), 0) / costs.length;
92
+ const stdDev = Math.sqrt(variance);
93
+ const cv = stdDev / mean;
94
+ // CV thresholds for confidence
95
+ if (cv < 0.5)
96
+ return "HIGH";
97
+ if (cv < 1.0)
98
+ return "MEDIUM";
99
+ return "LOW";
100
+ }
101
+ function generateReasons(tier, monthlyCost, activeDays, usage) {
102
+ const reasons = [];
103
+ // Usage frequency reason
104
+ if (activeDays < 5) {
105
+ reasons.push(`Light usage (${Math.round(activeDays)} active days/month)`);
106
+ }
107
+ else if (activeDays < 15) {
108
+ reasons.push(`Moderate usage (${Math.round(activeDays)} active days/month)`);
109
+ }
110
+ else {
111
+ reasons.push(`Heavy usage (${Math.round(activeDays)} active days/month)`);
112
+ }
113
+ // Cost-based reason
114
+ const breakEven = BREAK_EVEN[tier];
115
+ if (tier === "API Only") {
116
+ reasons.push(`API costs ($${monthlyCost.toFixed(0)}/mo) below subscription value`);
117
+ }
118
+ else if (monthlyCost > breakEven) {
119
+ reasons.push(`API costs ($${monthlyCost.toFixed(0)}/mo) exceed ${tier} break-even (~$${breakEven})`);
120
+ }
121
+ else {
122
+ reasons.push(`${tier} provides buffer for usage growth`);
123
+ }
124
+ // Model mix reason
125
+ const opusUsage = usage.modelBreakdown
126
+ .filter((m) => m.model.toLowerCase().includes("opus"))
127
+ .reduce((sum, m) => sum + m.percentage, 0);
128
+ if (opusUsage > 30) {
129
+ reasons.push(`High Opus usage (${opusUsage.toFixed(0)}%) benefits from subscription`);
130
+ }
131
+ return reasons;
132
+ }
@@ -0,0 +1,3 @@
1
+ import type { UsageData, Recommendation } from "./types.js";
2
+ export declare function renderRecommendation(usage: UsageData, rec: Recommendation, verbose: boolean): string;
3
+ export declare function renderJson(usage: UsageData, rec: Recommendation): string;
@@ -0,0 +1,189 @@
1
+ import pc from "picocolors";
2
+ const BOX_WIDTH = 58;
3
+ export function renderRecommendation(usage, rec, verbose) {
4
+ const lines = [];
5
+ // Header
6
+ lines.push(boxTop());
7
+ lines.push(boxMiddle(pc.bold("CoderPlan Tier Recommendation")));
8
+ lines.push(boxBottom());
9
+ lines.push("");
10
+ // Usage summary
11
+ lines.push(pc.bold("Your Usage") + pc.dim(` (Last ${usage.periodDays} Days)`));
12
+ lines.push(` Sessions: ${usage.sessionCount}`);
13
+ lines.push(` Active Days: ${usage.activeDays}/${usage.periodDays}`);
14
+ lines.push(` Total Tokens: ${formatTokens(usage.totalInputTokens + usage.totalOutputTokens)}`);
15
+ lines.push(` API Cost: $${usage.totalCost.toFixed(2)}`);
16
+ // Model breakdown
17
+ if (usage.modelBreakdown.length > 0) {
18
+ const modelStr = usage.modelBreakdown
19
+ .slice(0, 3)
20
+ .map((m) => `${simplifyModelName(m.model)} (${m.percentage.toFixed(0)}%)`)
21
+ .join(", ");
22
+ lines.push(` Primary Model: ${modelStr}`);
23
+ }
24
+ lines.push("");
25
+ // Recommendation
26
+ const tierColor = getTierColor(rec.tier);
27
+ lines.push(pc.bold("Recommendation: ") + tierColor(rec.tier) + pc.dim(` ($${getTierCost(rec.tier)}/month)`));
28
+ const confColor = getConfidenceColor(rec.confidence);
29
+ lines.push(` Confidence: ${confColor(rec.confidence)} ${pc.dim(getConfidenceNote(rec.confidence))}`);
30
+ lines.push("");
31
+ lines.push(" Reasoning:");
32
+ for (const reason of rec.reasons) {
33
+ lines.push(` ${pc.dim("-")} ${reason}`);
34
+ }
35
+ if (rec.savings > 0) {
36
+ lines.push("");
37
+ lines.push(` Projected Savings: ${pc.green(`~$${rec.savings.toFixed(0)}/month`)} vs API-only`);
38
+ }
39
+ lines.push("");
40
+ // Tier comparison table
41
+ lines.push(boxTop());
42
+ lines.push(boxMiddle("Tier Comparison"));
43
+ lines.push(boxSeparator());
44
+ for (const cost of rec.projectedCosts) {
45
+ const marker = cost.isRecommended ? pc.cyan(" <--") : "";
46
+ const costStr = `$${cost.monthlyCost.toFixed(0)}/month`;
47
+ const detail = cost.tier === "API Only"
48
+ ? "(current spend)"
49
+ : cost.overflowCost > 0
50
+ ? `($${cost.subscriptionCost} + ~$${cost.overflowCost.toFixed(0)} overflow)`
51
+ : "(no overflow expected)";
52
+ const line = `${padRight(cost.tier, 12)} ${padRight(costStr, 12)} ${pc.dim(detail)}${marker}`;
53
+ lines.push(boxContent(line));
54
+ }
55
+ lines.push(boxBottom());
56
+ // Verbose: show detailed metrics
57
+ if (verbose) {
58
+ lines.push("");
59
+ lines.push(pc.bold("Detailed Metrics"));
60
+ lines.push(` Input Tokens: ${formatTokens(usage.totalInputTokens)}`);
61
+ lines.push(` Output Tokens: ${formatTokens(usage.totalOutputTokens)}`);
62
+ lines.push(` Cache Creation: ${formatTokens(usage.totalCacheCreationTokens)}`);
63
+ lines.push(` Cache Read: ${formatTokens(usage.totalCacheReadTokens)}`);
64
+ lines.push("");
65
+ lines.push(pc.bold("Model Breakdown"));
66
+ for (const model of usage.modelBreakdown) {
67
+ lines.push(` ${padRight(simplifyModelName(model.model), 20)} $${model.cost.toFixed(2)} (${model.percentage.toFixed(1)}%)`);
68
+ }
69
+ }
70
+ return lines.join("\n");
71
+ }
72
+ export function renderJson(usage, rec) {
73
+ return JSON.stringify({
74
+ usage: {
75
+ periodDays: usage.periodDays,
76
+ activeDays: usage.activeDays,
77
+ sessionCount: usage.sessionCount,
78
+ totalCost: usage.totalCost,
79
+ totalTokens: usage.totalInputTokens + usage.totalOutputTokens,
80
+ modelBreakdown: usage.modelBreakdown,
81
+ },
82
+ recommendation: {
83
+ tier: rec.tier,
84
+ confidence: rec.confidence,
85
+ reasons: rec.reasons,
86
+ currentMonthlyCost: rec.currentMonthlyCost,
87
+ projectedSavings: rec.savings,
88
+ tierCosts: rec.projectedCosts,
89
+ },
90
+ }, null, 2);
91
+ }
92
+ // Box drawing helpers
93
+ function boxTop() {
94
+ return pc.dim("╭" + "─".repeat(BOX_WIDTH) + "╮");
95
+ }
96
+ function boxBottom() {
97
+ return pc.dim("╰" + "─".repeat(BOX_WIDTH) + "╯");
98
+ }
99
+ function boxSeparator() {
100
+ return pc.dim("├" + "─".repeat(BOX_WIDTH) + "┤");
101
+ }
102
+ function boxMiddle(text) {
103
+ const stripped = stripAnsi(text);
104
+ const padding = Math.max(0, BOX_WIDTH - stripped.length);
105
+ const left = Math.floor(padding / 2);
106
+ const right = padding - left;
107
+ return pc.dim("│") + " ".repeat(left) + text + " ".repeat(right) + pc.dim("│");
108
+ }
109
+ function boxContent(text) {
110
+ const stripped = stripAnsi(text);
111
+ const padding = Math.max(0, BOX_WIDTH - stripped.length - 1);
112
+ return pc.dim("│") + " " + text + " ".repeat(padding) + pc.dim("│");
113
+ }
114
+ function stripAnsi(str) {
115
+ // eslint-disable-next-line no-control-regex
116
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
117
+ }
118
+ function padRight(str, len) {
119
+ return str + " ".repeat(Math.max(0, len - str.length));
120
+ }
121
+ function formatTokens(tokens) {
122
+ if (tokens >= 1_000_000) {
123
+ return `${(tokens / 1_000_000).toFixed(1)}M`;
124
+ }
125
+ if (tokens >= 1_000) {
126
+ return `${(tokens / 1_000).toFixed(1)}K`;
127
+ }
128
+ return tokens.toString();
129
+ }
130
+ function simplifyModelName(model) {
131
+ const lower = model.toLowerCase();
132
+ if (lower.includes("opus"))
133
+ return "Opus";
134
+ if (lower.includes("sonnet"))
135
+ return "Sonnet";
136
+ if (lower.includes("haiku"))
137
+ return "Haiku";
138
+ return model.split("-").slice(0, 2).join(" ");
139
+ }
140
+ function getTierColor(tier) {
141
+ switch (tier) {
142
+ case "API Only":
143
+ return pc.dim;
144
+ case "Pro":
145
+ return pc.blue;
146
+ case "Max 5x":
147
+ return pc.cyan;
148
+ case "Max 20x":
149
+ return pc.magenta;
150
+ default:
151
+ return (s) => s;
152
+ }
153
+ }
154
+ function getTierCost(tier) {
155
+ switch (tier) {
156
+ case "Pro":
157
+ return 20;
158
+ case "Max 5x":
159
+ return 100;
160
+ case "Max 20x":
161
+ return 200;
162
+ default:
163
+ return 0;
164
+ }
165
+ }
166
+ function getConfidenceColor(confidence) {
167
+ switch (confidence) {
168
+ case "HIGH":
169
+ return pc.green;
170
+ case "MEDIUM":
171
+ return pc.yellow;
172
+ case "LOW":
173
+ return pc.red;
174
+ default:
175
+ return (s) => s;
176
+ }
177
+ }
178
+ function getConfidenceNote(confidence) {
179
+ switch (confidence) {
180
+ case "HIGH":
181
+ return "(consistent usage pattern)";
182
+ case "MEDIUM":
183
+ return "(some variation in usage)";
184
+ case "LOW":
185
+ return "(highly variable usage)";
186
+ default:
187
+ return "";
188
+ }
189
+ }
@@ -0,0 +1,41 @@
1
+ export interface UsageData {
2
+ totalCost: number;
3
+ totalInputTokens: number;
4
+ totalOutputTokens: number;
5
+ totalCacheCreationTokens: number;
6
+ totalCacheReadTokens: number;
7
+ sessionCount: number;
8
+ activeDays: number;
9
+ periodDays: number;
10
+ modelBreakdown: ModelUsage[];
11
+ dailyUsage: DailyUsage[];
12
+ }
13
+ export interface ModelUsage {
14
+ model: string;
15
+ cost: number;
16
+ inputTokens: number;
17
+ outputTokens: number;
18
+ percentage: number;
19
+ }
20
+ export interface DailyUsage {
21
+ date: string;
22
+ cost: number;
23
+ sessions: number;
24
+ }
25
+ export type Tier = "API Only" | "Pro" | "Max 5x" | "Max 20x";
26
+ export type Confidence = "HIGH" | "MEDIUM" | "LOW";
27
+ export interface Recommendation {
28
+ tier: Tier;
29
+ confidence: Confidence;
30
+ reasons: string[];
31
+ projectedCosts: TierCost[];
32
+ currentMonthlyCost: number;
33
+ savings: number;
34
+ }
35
+ export interface TierCost {
36
+ tier: Tier;
37
+ monthlyCost: number;
38
+ subscriptionCost: number;
39
+ overflowCost: number;
40
+ isRecommended: boolean;
41
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "coderplan-tier",
3
+ "version": "0.1.0",
4
+ "description": "Analyze Claude Code usage and get tier recommendations",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "coderplan-tier": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "start": "node dist/index.js",
16
+ "dev": "tsx src/index.ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "usage",
23
+ "pricing",
24
+ "tier"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "commander": "^12.0.0",
30
+ "picocolors": "^1.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.0.0",
34
+ "tsx": "^4.0.0",
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ }
40
+ }