@pkprosol/coach 1.0.5 → 1.0.6

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 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"
@@ -303,6 +334,9 @@ async function main() {
303
334
  case "focus":
304
335
  await handleFocus();
305
336
  break;
337
+ case "costs":
338
+ await handleCosts();
339
+ break;
306
340
  case "recap":
307
341
  handleRecap();
308
342
  break;
@@ -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>;
@@ -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 sessionSummaries = data.sessions.map((s) => ({
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) {
@@ -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;
@@ -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
+ }
@@ -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
@@ -8,4 +8,5 @@ export const DIMENSIONS = [
8
8
  "Communication Style",
9
9
  "Tool Leverage",
10
10
  "Problem Decomposition",
11
+ "Cost Awareness",
11
12
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkprosol/coach",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Daily AI work coach — analyzes your Claude Code & Claude App sessions to deliver one lesson + one tip daily",
5
5
  "type": "module",
6
6
  "bin": {