@pkprosol/coach 1.0.5 → 1.0.7
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/dist/bin/coach.js +38 -2
- package/dist/src/analyzer.d.ts +3 -1
- package/dist/src/analyzer.js +83 -1
- package/dist/src/display.d.ts +2 -1
- package/dist/src/display.js +40 -0
- package/dist/src/types.d.ts +18 -1
- package/dist/src/types.js +1 -0
- package/package.json +1 -1
package/dist/bin/coach.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createInterface } from "node:readline";
|
|
|
3
3
|
import ora from "ora";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { collectToday } from "../src/collector.js";
|
|
6
|
-
import { analyze, runClaude, buildHandoffPrompt, buildFocusPrompt } from "../src/analyzer.js";
|
|
7
|
-
import { renderInsight, renderStreak, renderHistory, renderNoData, renderError, renderHandoff, renderFocus, renderRecap, renderGoals, renderCompare, renderWelcome, } from "../src/display.js";
|
|
6
|
+
import { analyze, runClaude, buildHandoffPrompt, buildFocusPrompt, buildCostsPrompt, estimateCosts } from "../src/analyzer.js";
|
|
7
|
+
import { renderInsight, renderStreak, renderHistory, renderNoData, renderError, renderHandoff, renderFocus, renderRecap, renderGoals, renderCompare, renderWelcome, renderCosts, } from "../src/display.js";
|
|
8
8
|
import { isFirstRun, loadState, saveState, loadInsights, appendInsight, updateLastInsightRating, updateStreak, recordDimension, addGoal, completeGoal, clearCompletedGoals, recordDailyStat, } from "../src/storage.js";
|
|
9
9
|
function askQuestion(prompt) {
|
|
10
10
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -176,6 +176,36 @@ async function handleFocus() {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
async function handleCosts() {
|
|
180
|
+
const spinner = ora({ text: "Analyzing costs and token usage...", color: "cyan" }).start();
|
|
181
|
+
const data = collectToday();
|
|
182
|
+
if (data.prompts.length === 0) {
|
|
183
|
+
spinner.stop();
|
|
184
|
+
console.log(renderNoData());
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
spinner.text = "Crunching cost data...";
|
|
188
|
+
try {
|
|
189
|
+
const costs = estimateCosts(data);
|
|
190
|
+
const prompt = buildCostsPrompt(data, costs);
|
|
191
|
+
const text = await runClaude(prompt);
|
|
192
|
+
spinner.stop();
|
|
193
|
+
const cleaned = text.replace(/^```json?\s*/, "").replace(/\s*```$/, "").trim();
|
|
194
|
+
const analysis = JSON.parse(cleaned);
|
|
195
|
+
console.log("");
|
|
196
|
+
console.log(renderCosts(analysis));
|
|
197
|
+
console.log("");
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
spinner.stop();
|
|
201
|
+
if (err.message?.includes("claude CLI not found")) {
|
|
202
|
+
console.log(renderError("claude CLI not found. Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code"));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
console.log(renderError(err.message ?? "Cost analysis failed."));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
179
209
|
function handleRecap() {
|
|
180
210
|
const data = collectToday();
|
|
181
211
|
if (data.prompts.length === 0) {
|
|
@@ -275,6 +305,7 @@ ${chalk.bold("Usage:")}
|
|
|
275
305
|
coach Today's lesson + tip (default)
|
|
276
306
|
coach handoff Generate a handoff note for your current work
|
|
277
307
|
coach focus Analyze context-switching and focus patterns
|
|
308
|
+
coach costs Token costs, prompt engineering tips & LLM insights
|
|
278
309
|
coach recap Quick summary of today's stats (no AI)
|
|
279
310
|
coach goals Show current goals
|
|
280
311
|
coach goals set Add a goal: coach goals set "finish auth"
|
|
@@ -294,6 +325,8 @@ async function main() {
|
|
|
294
325
|
console.log("");
|
|
295
326
|
console.log(renderWelcome());
|
|
296
327
|
console.log("");
|
|
328
|
+
// Save initial state so isFirstRun() returns false next time
|
|
329
|
+
saveState(loadState());
|
|
297
330
|
return;
|
|
298
331
|
}
|
|
299
332
|
switch (command) {
|
|
@@ -303,6 +336,9 @@ async function main() {
|
|
|
303
336
|
case "focus":
|
|
304
337
|
await handleFocus();
|
|
305
338
|
break;
|
|
339
|
+
case "costs":
|
|
340
|
+
await handleCosts();
|
|
341
|
+
break;
|
|
306
342
|
case "recap":
|
|
307
343
|
handleRecap();
|
|
308
344
|
break;
|
package/dist/src/analyzer.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import type { CollectedData, Dimension, Insight, StoredInsight } from "./types.js";
|
|
1
|
+
import type { CollectedData, CostEstimate, Dimension, Insight, StoredInsight } from "./types.js";
|
|
2
|
+
export declare function estimateCosts(data: CollectedData): CostEstimate[];
|
|
2
3
|
export declare function runClaude(prompt: string): Promise<string>;
|
|
3
4
|
export declare function buildHandoffPrompt(data: CollectedData): string;
|
|
4
5
|
export declare function buildFocusPrompt(data: CollectedData): string;
|
|
6
|
+
export declare function buildCostsPrompt(data: CollectedData, costs: CostEstimate[]): string;
|
|
5
7
|
export declare function analyze(data: CollectedData, recentDimensions: Dimension[], pastInsights: StoredInsight[]): Promise<Insight>;
|
package/dist/src/analyzer.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { DIMENSIONS } from "./types.js";
|
|
3
|
+
// Claude Sonnet pricing (most common for Claude Code)
|
|
4
|
+
const INPUT_COST_PER_MTOK = 3; // $3 per million input tokens
|
|
5
|
+
const OUTPUT_COST_PER_MTOK = 15; // $15 per million output tokens
|
|
6
|
+
export function estimateCosts(data) {
|
|
7
|
+
return data.sessions.map((s) => {
|
|
8
|
+
const inputCost = (s.inputTokens / 1_000_000) * INPUT_COST_PER_MTOK;
|
|
9
|
+
const outputCost = (s.outputTokens / 1_000_000) * OUTPUT_COST_PER_MTOK;
|
|
10
|
+
return {
|
|
11
|
+
sessionId: s.sessionId.slice(0, 8),
|
|
12
|
+
project: s.project,
|
|
13
|
+
inputCost,
|
|
14
|
+
outputCost,
|
|
15
|
+
totalCost: inputCost + outputCost,
|
|
16
|
+
inputTokens: s.inputTokens,
|
|
17
|
+
outputTokens: s.outputTokens,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
3
21
|
export function runClaude(prompt) {
|
|
4
22
|
return new Promise((resolve, reject) => {
|
|
5
23
|
const proc = spawn("claude", ["-p", "--output-format", "text"], {
|
|
@@ -75,6 +93,9 @@ function pickDimension(recentDimensions, data) {
|
|
|
75
93
|
if (data.sessions.length === 1 && data.prompts.length > 5) {
|
|
76
94
|
scores.set("Problem Decomposition", (scores.get("Problem Decomposition") ?? 0) + 1.5);
|
|
77
95
|
}
|
|
96
|
+
if (data.totalTokens > 100000) {
|
|
97
|
+
scores.set("Cost Awareness", (scores.get("Cost Awareness") ?? 0) + 1.5);
|
|
98
|
+
}
|
|
78
99
|
// Pick highest scoring available dimension
|
|
79
100
|
let best = pool[0];
|
|
80
101
|
let bestScore = -1;
|
|
@@ -93,13 +114,15 @@ function buildPrompt(data, dimension, pastInsights) {
|
|
|
93
114
|
project: p.project,
|
|
94
115
|
sessionId: p.sessionId.slice(0, 8),
|
|
95
116
|
}));
|
|
96
|
-
const
|
|
117
|
+
const costs = estimateCosts(data);
|
|
118
|
+
const sessionSummaries = data.sessions.map((s, i) => ({
|
|
97
119
|
project: s.project,
|
|
98
120
|
messages: s.messageCount,
|
|
99
121
|
userMessages: s.userMessageCount,
|
|
100
122
|
toolCalls: s.toolCallCount,
|
|
101
123
|
tools: s.toolNames,
|
|
102
124
|
tokens: s.inputTokens + s.outputTokens,
|
|
125
|
+
estimatedCost: `$${costs[i]?.totalCost.toFixed(4) ?? "0.0000"}`,
|
|
103
126
|
duration: s.startTime && s.endTime
|
|
104
127
|
? `${Math.round((new Date(s.endTime).getTime() - new Date(s.startTime).getTime()) / 60000)}min`
|
|
105
128
|
: "unknown",
|
|
@@ -127,6 +150,7 @@ Dimension descriptions:
|
|
|
127
150
|
- Communication Style: how problems are described to Claude
|
|
128
151
|
- Tool Leverage: effective use of Claude's capabilities (tools, features)
|
|
129
152
|
- Problem Decomposition: breaking down vs monolithic asks
|
|
153
|
+
- Cost Awareness: token efficiency, cost-effective prompting, understanding what makes prompts expensive or cheap
|
|
130
154
|
|
|
131
155
|
## Today's Session Data
|
|
132
156
|
|
|
@@ -136,6 +160,7 @@ Total sessions: ${data.sessions.length}
|
|
|
136
160
|
Total messages: ${data.totalMessages}
|
|
137
161
|
Total tool calls: ${data.totalToolCalls}
|
|
138
162
|
Total tokens: ${data.totalTokens.toLocaleString()}
|
|
163
|
+
Estimated total cost: $${costs.reduce((s, c) => s + c.totalCost, 0).toFixed(4)} (Sonnet pricing)
|
|
139
164
|
|
|
140
165
|
### User Prompts (chronological)
|
|
141
166
|
${JSON.stringify(samplePrompts, null, 2)}
|
|
@@ -251,6 +276,63 @@ Analyze the patterns and return a JSON object:
|
|
|
251
276
|
}
|
|
252
277
|
|
|
253
278
|
Be specific and reference actual project names and times.
|
|
279
|
+
Respond with ONLY the JSON object, no markdown fences or other text.`;
|
|
280
|
+
}
|
|
281
|
+
export function buildCostsPrompt(data, costs) {
|
|
282
|
+
const totalCost = costs.reduce((s, c) => s + c.totalCost, 0);
|
|
283
|
+
const totalInput = costs.reduce((s, c) => s + c.inputTokens, 0);
|
|
284
|
+
const totalOutput = costs.reduce((s, c) => s + c.outputTokens, 0);
|
|
285
|
+
const totalInputCost = costs.reduce((s, c) => s + c.inputCost, 0);
|
|
286
|
+
const totalOutputCost = costs.reduce((s, c) => s + c.outputCost, 0);
|
|
287
|
+
const samplePrompts = data.prompts.slice(0, 30).map((p) => ({
|
|
288
|
+
text: p.text.slice(0, 400),
|
|
289
|
+
project: p.project,
|
|
290
|
+
charLength: p.text.length,
|
|
291
|
+
}));
|
|
292
|
+
const sessionCosts = costs.map((c) => ({
|
|
293
|
+
project: c.project,
|
|
294
|
+
inputTokens: c.inputTokens,
|
|
295
|
+
outputTokens: c.outputTokens,
|
|
296
|
+
estimatedCost: `$${c.totalCost.toFixed(4)}`,
|
|
297
|
+
}));
|
|
298
|
+
return `You are a cost and prompt engineering analyst for Claude Code usage. Analyze this developer's token usage and costs to provide actionable insights about efficiency, prompt engineering, and how LLMs process their requests.
|
|
299
|
+
|
|
300
|
+
## Today's Cost Data
|
|
301
|
+
|
|
302
|
+
Date: ${data.date}
|
|
303
|
+
Total estimated cost: $${totalCost.toFixed(4)} (using Sonnet pricing: $${INPUT_COST_PER_MTOK}/MTok input, $${OUTPUT_COST_PER_MTOK}/MTok output)
|
|
304
|
+
Total input tokens: ${totalInput.toLocaleString()} ($${totalInputCost.toFixed(4)})
|
|
305
|
+
Total output tokens: ${totalOutput.toLocaleString()} ($${totalOutputCost.toFixed(4)})
|
|
306
|
+
Total sessions: ${data.sessions.length}
|
|
307
|
+
Total prompts: ${data.prompts.length}
|
|
308
|
+
Total tool calls: ${data.totalToolCalls}
|
|
309
|
+
|
|
310
|
+
### Per-Session Costs
|
|
311
|
+
${JSON.stringify(sessionCosts, null, 2)}
|
|
312
|
+
|
|
313
|
+
### User Prompts (with character lengths)
|
|
314
|
+
${JSON.stringify(samplePrompts, null, 2)}
|
|
315
|
+
|
|
316
|
+
## Your Task
|
|
317
|
+
|
|
318
|
+
Analyze the cost patterns and return a JSON object with exactly these fields:
|
|
319
|
+
|
|
320
|
+
{
|
|
321
|
+
"estimatedCost": "Total estimated cost as a readable string (e.g. '$0.42')",
|
|
322
|
+
"mostExpensiveSession": "Describe which session cost the most and why (project name, what drove the cost — long prompts, many tool calls, etc). 2-3 sentences.",
|
|
323
|
+
"costBreakdown": "Explain the input vs output token split and what it means. Are they paying more for long prompts (input) or for Claude's responses (output)? What does the ratio tell us? 2-3 sentences.",
|
|
324
|
+
"surprisingFact": "One interesting or surprising observation about their usage — could be about token economics, how LLMs tokenize text, why certain prompts cost more, context window mechanics, or prompt caching. Make it genuinely educational. 2-3 sentences.",
|
|
325
|
+
"efficiencyTips": ["Tip 1", "Tip 2", "Tip 3"],
|
|
326
|
+
"promptEngineeringInsight": "A genuinely interesting insight about prompt engineering, how LLMs work, or token economics that relates to their specific usage patterns. Teach them something they probably don't know. 2-3 sentences."
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
Guidelines:
|
|
330
|
+
- Be specific — reference actual projects, prompt lengths, and cost numbers from the data.
|
|
331
|
+
- For efficiencyTips, give concrete, actionable advice (not generic "write shorter prompts").
|
|
332
|
+
- For surprisingFact and promptEngineeringInsight, teach something genuinely interesting about LLMs, tokenization, attention mechanisms, or prompt engineering that connects to their data.
|
|
333
|
+
- Keep costs in perspective — compare to a cup of coffee, a SaaS subscription, etc.
|
|
334
|
+
- If tool calls are a significant portion of the work, explain how tool use affects costs (each tool result is input tokens on the next turn).
|
|
335
|
+
|
|
254
336
|
Respond with ONLY the JSON object, no markdown fences or other text.`;
|
|
255
337
|
}
|
|
256
338
|
export async function analyze(data, recentDimensions, pastInsights) {
|
package/dist/src/display.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Insight, CoachState, StoredInsight, Goal, CollectedData, DailyStat } from "./types.js";
|
|
1
|
+
import type { Insight, CoachState, StoredInsight, Goal, CollectedData, DailyStat, CostAnalysis } from "./types.js";
|
|
2
2
|
export declare function renderInsight(insight: Insight, state: CoachState): string;
|
|
3
3
|
export declare function renderStreak(state: CoachState): string;
|
|
4
4
|
export declare function renderHistory(insights: StoredInsight[]): string;
|
|
@@ -22,3 +22,4 @@ export declare function renderFocus(focus: {
|
|
|
22
22
|
export declare function renderRecap(data: CollectedData): string;
|
|
23
23
|
export declare function renderGoals(goals: Goal[]): string;
|
|
24
24
|
export declare function renderCompare(today: DailyStat, avg: DailyStat): string;
|
|
25
|
+
export declare function renderCosts(analysis: CostAnalysis): string;
|
package/dist/src/display.js
CHANGED
|
@@ -145,6 +145,7 @@ export function renderWelcome() {
|
|
|
145
145
|
out.push(padLine(" " + chalk.cyan("coach") + " Today's lesson + tip"));
|
|
146
146
|
out.push(padLine(" " + chalk.cyan("coach handoff") + " Handoff note for your work"));
|
|
147
147
|
out.push(padLine(" " + chalk.cyan("coach focus") + " Focus & context-switching"));
|
|
148
|
+
out.push(padLine(" " + chalk.cyan("coach costs") + " Cost analysis & LLM tips"));
|
|
148
149
|
out.push(padLine(" " + chalk.cyan("coach recap") + " Quick stats (no AI)"));
|
|
149
150
|
out.push(padLine(" " + chalk.cyan("coach goals") + " Track your goals"));
|
|
150
151
|
out.push(padLine(" " + chalk.cyan("coach compare") + " Today vs recent averages"));
|
|
@@ -314,3 +315,42 @@ export function renderCompare(today, avg) {
|
|
|
314
315
|
out.push(boxBot());
|
|
315
316
|
return out.join("\n");
|
|
316
317
|
}
|
|
318
|
+
// === Cost Analysis ===
|
|
319
|
+
export function renderCosts(analysis) {
|
|
320
|
+
const out = [];
|
|
321
|
+
out.push(boxTop());
|
|
322
|
+
out.push(padLine(chalk.bold.white(" COST ANALYSIS")));
|
|
323
|
+
out.push(boxMid());
|
|
324
|
+
// Estimated cost
|
|
325
|
+
out.push(padLine(""));
|
|
326
|
+
out.push(padLine(` 💰 Estimated cost today: ${chalk.bold.yellow(analysis.estimatedCost)}`));
|
|
327
|
+
// Most expensive session
|
|
328
|
+
out.push(...renderSection(" 📊 Most Expensive Session", analysis.mostExpensiveSession));
|
|
329
|
+
// Cost breakdown
|
|
330
|
+
out.push(...renderSection(" ⚖️ Input vs Output", analysis.costBreakdown));
|
|
331
|
+
// Surprising fact
|
|
332
|
+
out.push(padLine(""));
|
|
333
|
+
out.push(padLine(chalk.bold(" 🤯 Did You Know?")));
|
|
334
|
+
for (const line of wrapText(analysis.surprisingFact, WIDTH - 4)) {
|
|
335
|
+
out.push(padLine(" " + chalk.cyan(line)));
|
|
336
|
+
}
|
|
337
|
+
// Efficiency tips
|
|
338
|
+
if (analysis.efficiencyTips.length > 0) {
|
|
339
|
+
out.push(padLine(""));
|
|
340
|
+
out.push(padLine(chalk.bold(" 💡 Efficiency Tips")));
|
|
341
|
+
for (const tip of analysis.efficiencyTips) {
|
|
342
|
+
for (const line of wrapText(`- ${tip}`, WIDTH - 6)) {
|
|
343
|
+
out.push(padLine(" " + line));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Prompt engineering insight
|
|
348
|
+
out.push(padLine(""));
|
|
349
|
+
out.push(padLine(chalk.bold(" 🧠 Prompt Engineering")));
|
|
350
|
+
for (const line of wrapText(analysis.promptEngineeringInsight, WIDTH - 4)) {
|
|
351
|
+
out.push(padLine(" " + chalk.italic(line)));
|
|
352
|
+
}
|
|
353
|
+
out.push(padLine(""));
|
|
354
|
+
out.push(boxBot());
|
|
355
|
+
return out.join("\n");
|
|
356
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const DIMENSIONS: readonly ["Prompting Craft", "Workflow Efficiency", "Architecture Thinking", "Learning Patterns", "Focus & Deep Work", "Communication Style", "Tool Leverage", "Problem Decomposition"];
|
|
1
|
+
export declare const DIMENSIONS: readonly ["Prompting Craft", "Workflow Efficiency", "Architecture Thinking", "Learning Patterns", "Focus & Deep Work", "Communication Style", "Tool Leverage", "Problem Decomposition", "Cost Awareness"];
|
|
2
2
|
export type Dimension = (typeof DIMENSIONS)[number];
|
|
3
3
|
export interface UserPrompt {
|
|
4
4
|
text: string;
|
|
@@ -70,6 +70,23 @@ export interface CoachState {
|
|
|
70
70
|
goals: Goal[];
|
|
71
71
|
dailyStats: DailyStat[];
|
|
72
72
|
}
|
|
73
|
+
export interface CostEstimate {
|
|
74
|
+
sessionId: string;
|
|
75
|
+
project: string;
|
|
76
|
+
inputCost: number;
|
|
77
|
+
outputCost: number;
|
|
78
|
+
totalCost: number;
|
|
79
|
+
inputTokens: number;
|
|
80
|
+
outputTokens: number;
|
|
81
|
+
}
|
|
82
|
+
export interface CostAnalysis {
|
|
83
|
+
estimatedCost: string;
|
|
84
|
+
mostExpensiveSession: string;
|
|
85
|
+
costBreakdown: string;
|
|
86
|
+
surprisingFact: string;
|
|
87
|
+
efficiencyTips: string[];
|
|
88
|
+
promptEngineeringInsight: string;
|
|
89
|
+
}
|
|
73
90
|
export interface HistoryEntry {
|
|
74
91
|
display: string;
|
|
75
92
|
pastedContents: Record<string, unknown>;
|
package/dist/src/types.js
CHANGED