Install
$ agentstack add skill-stevederico-skills-code-count ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Claude Code Usage Stats
Count tokens and estimate equivalent API cost from local Claude Code JSONL transcripts stored in ~/.claude/projects/.
How it works
Claude Code stores conversation transcripts as JSONL files in ~/.claude/projects//*.jsonl. Each line is a JSON object. Lines with assistant messages contain a message object with a usage block tracking token consumption for that API call.
Deduplication
Streaming responses write multiple JSONL lines for the same API call with cumulative token counts. To avoid double-counting, deduplicate using messageId:requestId as a composite key and keep only the last entry per key.
messageId=message.idfieldrequestId= top-levelrequestIdfield- If either is missing, the entry is always counted (never deduped)
Token fields
| Field | Description | |-------|-------------| | input_tokens | Direct input tokens (small — most context hits cache) | | cache_creation_input_tokens | Tokens written to prompt cache (costs 1.25x input) | | cache_read_input_tokens | Tokens read from prompt cache (costs 0.1x input) | | output_tokens | Generated output tokens |
Timestamps
All timestamps in the JSONL are UTC. Always convert to the user's local timezone before bucketing by date. Use datetime.now(timezone.utc).astimezone().tzinfo to auto-detect local timezone.
Optional: costUSD
Some transcripts include a pre-calculated costUSD field per entry. If present, sum those instead of computing from tokens. Pro/Max subscriptions typically don't populate this field.
Opus API pricing (per 1M tokens)
| Type | Cost | |------|------| | Input | $15 | | Cache creation | $18.75 | | Cache read | $1.50 | | Output | $75 |
When the user asks for usage stats
Run the following script via python3 -c. Adjust the days variable if the user specifies a time range (default: 30 days). Present results as a formatted table.
import json, os, glob
from datetime import datetime, timedelta, timezone
LOCAL_TZ = datetime.now(timezone.utc).astimezone().tzinfo
days = 30
cutoff = (datetime.now(timezone.utc) - timedelta(days=days)).isoformat()
def to_local_date(ts):
if not ts:
return None
if ts.endswith('Z'):
dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
elif '+' in ts[10:] or ts.count('-') > 2:
dt = datetime.fromisoformat(ts)
else:
dt = datetime.fromisoformat(ts).replace(tzinfo=timezone.utc)
return dt.astimezone(LOCAL_TZ).strftime('%Y-%m-%d')
messages = {}
for path in glob.glob(os.path.expanduser('~/.claude/projects/*/*.jsonl')):
with open(path) as f:
for line in f:
try:
obj = json.loads(line.strip())
mid = obj.get('message', {}).get('id')
rid = obj.get('requestId')
if not mid or not rid:
continue
u = obj.get('message', {}).get('usage', {})
cost = obj.get('costUSD')
if not u:
continue
ts = obj.get('timestamp') or obj.get('message', {}).get('created_at') or ''
if ts 15,}')
print(f'Input tokens: {total_in:>15,}')
print(f'Cache creation: {total_cache_create:>15,}')
print(f'Cache reads: {total_cache_read:>15,}')
print(f'Output tokens: {total_out:>15,}')
if cost_entries > 0:
print(f'Pre-calculated cost: ${total_cost:>14,.2f}')
else:
cost = (total_in/1e6*15 + total_cache_create/1e6*18.75 + total_cache_read/1e6*1.50 + total_out/1e6*75)
print(f'Estimated API cost: ${cost:>14,.2f} (Opus rates)')
When the user asks for a daily breakdown
Run the same parsing logic above, then bucket by local date and display a bar chart:
from collections import defaultdict
daily = defaultdict(float)
for v in messages.values():
u = v['usage']
cost = (u.get('input_tokens',0)/1e6*15 + u.get('cache_creation_input_tokens',0)/1e6*18.75
+ u.get('cache_read_input_tokens',0)/1e6*1.50 + u.get('output_tokens',0)/1e6*75)
daily[v['date']] += cost
start = datetime.strptime(min(daily.keys()), '%Y-%m-%d')
end = datetime.strptime(max(daily.keys()), '%Y-%m-%d')
all_days = []
d = start
while d 0 else 0
day = datetime.strptime(date, '%Y-%m-%d').strftime('%a')
print(f'{date} {day} {"█" * bars} ${cost:>7,.0f}')
print(f'\nTotal: ${sum(c for _, c in all_days):>,.0f}')
When the user asks for per-project breakdown
from collections import defaultdict
projects = defaultdict(lambda: {'calls': 0, 'in': 0, 'out': 0, 'cache_create': 0, 'cache_read': 0})
for path in glob.glob(os.path.expanduser('~/.claude/projects/*/*.jsonl')):
project = os.path.basename(os.path.dirname(path))
file_msgs = {}
with open(path) as f:
for line in f:
try:
obj = json.loads(line.strip())
mid = obj.get('message', {}).get('id')
rid = obj.get('requestId')
if not mid or not rid:
continue
u = obj.get('message', {}).get('usage', {})
if not u:
continue
file_msgs[f'{mid}:{rid}'] = u
except:
pass
p = projects[project]
for u in file_msgs.values():
p['calls'] += 1
p['in'] += u.get('input_tokens', 0)
p['out'] += u.get('output_tokens', 0)
p['cache_create'] += u.get('cache_creation_input_tokens', 0)
p['cache_read'] += u.get('cache_read_input_tokens', 0)
for name in sorted(projects, key=lambda k: projects[k]['out'], reverse=True):
p = projects[name]
short = name.split('-')[-1] if name.count('-') > 3 else name
cost = (p['in']/1e6*15 + p['cache_create']/1e6*18.75 + p['cache_read']/1e6*1.5 + p['out']/1e6*75)
print(f'{short:6} calls {p["out"]:>10,} out tokens ${cost:>8,.2f}')
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: stevederico
- Source: stevederico/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.