inscript 0.4.0__tar.gz → 0.5.0__tar.gz
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.
- {inscript-0.4.0 → inscript-0.5.0}/PKG-INFO +1 -1
- {inscript-0.4.0 → inscript-0.5.0}/inscript.egg-info/PKG-INFO +1 -1
- {inscript-0.4.0 → inscript-0.5.0}/inscript_pkg/__init__.py +32 -2
- {inscript-0.4.0 → inscript-0.5.0}/inscript_pkg/hook.py +82 -13
- {inscript-0.4.0 → inscript-0.5.0}/pyproject.toml +1 -1
- {inscript-0.4.0 → inscript-0.5.0}/LICENSE +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/README.md +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/inscript.egg-info/SOURCES.txt +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/inscript.egg-info/dependency_links.txt +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/inscript.egg-info/entry_points.txt +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/inscript.egg-info/top_level.txt +0 -0
- {inscript-0.4.0 → inscript-0.5.0}/setup.cfg +0 -0
|
@@ -17,7 +17,7 @@ import time
|
|
|
17
17
|
from datetime import datetime, timezone
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
-
__version__ = "0.
|
|
20
|
+
__version__ = "0.5.0"
|
|
21
21
|
|
|
22
22
|
INSCRIPT_DIR = Path.home() / ".inscript"
|
|
23
23
|
ACTIVE_PROJECT_FILE = INSCRIPT_DIR / "active_project"
|
|
@@ -86,6 +86,15 @@ def session_dir(session_id: str) -> Path:
|
|
|
86
86
|
return SESSIONS_DIR / session_id
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
def _format_tokens(n: int) -> str:
|
|
90
|
+
"""Format token count: 1234 -> 1.2k, 1234567 -> 1.2M."""
|
|
91
|
+
if n >= 1_000_000:
|
|
92
|
+
return f"{n / 1_000_000:.1f}M"
|
|
93
|
+
elif n >= 1_000:
|
|
94
|
+
return f"{n / 1_000:.1f}k"
|
|
95
|
+
return str(n)
|
|
96
|
+
|
|
97
|
+
|
|
89
98
|
def list_sessions() -> list[dict]:
|
|
90
99
|
sessions = []
|
|
91
100
|
if not SESSIONS_DIR.exists():
|
|
@@ -279,7 +288,20 @@ def _cmd_log(session_id: str | None):
|
|
|
279
288
|
except json.JSONDecodeError:
|
|
280
289
|
pass
|
|
281
290
|
|
|
282
|
-
|
|
291
|
+
# Show token usage if summary exists
|
|
292
|
+
summary_file = sdir / "summary.json"
|
|
293
|
+
token_str = ""
|
|
294
|
+
if summary_file.exists():
|
|
295
|
+
try:
|
|
296
|
+
s = json.loads(summary_file.read_text())
|
|
297
|
+
tokens = s.get("tokens")
|
|
298
|
+
if tokens:
|
|
299
|
+
total = tokens.get("total_tokens", 0)
|
|
300
|
+
token_str = f", {_format_tokens(total)} tokens"
|
|
301
|
+
except (json.JSONDecodeError, OSError):
|
|
302
|
+
pass
|
|
303
|
+
|
|
304
|
+
print(f" {len(files_seen)} files, {edits} edits, {len(prompts)} prompts{token_str}")
|
|
283
305
|
|
|
284
306
|
|
|
285
307
|
def _cmd_tag(tag_name: str | None):
|
|
@@ -529,10 +551,18 @@ def _cmd_export(session_id: str):
|
|
|
529
551
|
if summary_file.exists():
|
|
530
552
|
s = json.loads(summary_file.read_text())
|
|
531
553
|
print(f"- **Ended**: {s.get('end_time', '?')}")
|
|
554
|
+
if s.get("duration_seconds"):
|
|
555
|
+
print(f"- **Duration**: {_format_duration(s['duration_seconds'])}")
|
|
532
556
|
print(f"- **Prompts**: {s.get('prompts', '?')}")
|
|
533
557
|
print(f"- **Files read**: {s.get('files_read', '?')}")
|
|
534
558
|
print(f"- **Files written**: {s.get('files_written', '?')}")
|
|
535
559
|
print(f"- **Total edits**: {s.get('total_edits', '?')}")
|
|
560
|
+
tokens = s.get("tokens")
|
|
561
|
+
if tokens:
|
|
562
|
+
print(f"- **Model**: {tokens.get('model', '?')}")
|
|
563
|
+
print(f"- **Tokens**: {_format_tokens(tokens.get('total_tokens', 0))} total ({_format_tokens(tokens.get('input_tokens', 0))} in, {_format_tokens(tokens.get('output_tokens', 0))} out)")
|
|
564
|
+
if tokens.get("cache_read_tokens"):
|
|
565
|
+
print(f"- **Cache**: {_format_tokens(tokens['cache_read_tokens'])} read, {_format_tokens(tokens.get('cache_write_tokens', 0))} written")
|
|
536
566
|
|
|
537
567
|
# Load prompts, touches, and diffs
|
|
538
568
|
prompts = _load_prompts(sdir)
|
|
@@ -137,6 +137,12 @@ def handle_session_start(data: dict) -> None:
|
|
|
137
137
|
"project": proj,
|
|
138
138
|
"status": "active",
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
# Save transcript path if provided (for token counting on Stop)
|
|
142
|
+
transcript_path = data.get("transcript_path")
|
|
143
|
+
if transcript_path:
|
|
144
|
+
meta["transcript_path"] = transcript_path
|
|
145
|
+
|
|
140
146
|
(sdir / "meta.json").write_text(json.dumps(meta, indent=2))
|
|
141
147
|
|
|
142
148
|
# Mark as active session
|
|
@@ -216,17 +222,23 @@ def handle_post_tool_use(data: dict) -> None:
|
|
|
216
222
|
|
|
217
223
|
sdir = SESSIONS_DIR / session_id
|
|
218
224
|
|
|
219
|
-
# Update session meta with project if
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
225
|
+
# Update session meta with project + transcript_path if needed
|
|
226
|
+
meta_file = sdir / "meta.json"
|
|
227
|
+
if meta_file.exists():
|
|
228
|
+
try:
|
|
229
|
+
meta = json.loads(meta_file.read_text())
|
|
230
|
+
changed = False
|
|
231
|
+
if root and meta.get("project") != str(root):
|
|
232
|
+
meta["project"] = str(root)
|
|
233
|
+
changed = True
|
|
234
|
+
transcript_path = data.get("transcript_path")
|
|
235
|
+
if transcript_path and not meta.get("transcript_path"):
|
|
236
|
+
meta["transcript_path"] = transcript_path
|
|
237
|
+
changed = True
|
|
238
|
+
if changed:
|
|
239
|
+
meta_file.write_text(json.dumps(meta, indent=2))
|
|
240
|
+
except (json.JSONDecodeError, OSError):
|
|
241
|
+
pass
|
|
230
242
|
|
|
231
243
|
# Relativize path for readability
|
|
232
244
|
display_path = file_path
|
|
@@ -314,6 +326,52 @@ def _check_overlap(current_session: str, project: str, file_path: str) -> None:
|
|
|
314
326
|
# Stop handler
|
|
315
327
|
# ---------------------------------------------------------------------------
|
|
316
328
|
|
|
329
|
+
def _compute_token_usage(transcript_path: str) -> dict | None:
|
|
330
|
+
"""Read a Claude Code transcript JSONL and sum token usage."""
|
|
331
|
+
try:
|
|
332
|
+
tp = Path(transcript_path)
|
|
333
|
+
if not tp.exists():
|
|
334
|
+
return None
|
|
335
|
+
|
|
336
|
+
total_input = 0
|
|
337
|
+
total_output = 0
|
|
338
|
+
total_cache_read = 0
|
|
339
|
+
total_cache_write = 0
|
|
340
|
+
model = None
|
|
341
|
+
|
|
342
|
+
for line in tp.open():
|
|
343
|
+
try:
|
|
344
|
+
d = json.loads(line)
|
|
345
|
+
msg = d.get("message", {})
|
|
346
|
+
usage = msg.get("usage")
|
|
347
|
+
if not usage:
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
if model is None:
|
|
351
|
+
model = msg.get("model")
|
|
352
|
+
|
|
353
|
+
total_input += usage.get("input_tokens", 0)
|
|
354
|
+
total_output += usage.get("output_tokens", 0)
|
|
355
|
+
total_cache_read += usage.get("cache_read_input_tokens", 0)
|
|
356
|
+
total_cache_write += usage.get("cache_creation_input_tokens", 0)
|
|
357
|
+
except json.JSONDecodeError:
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
if total_input == 0 and total_output == 0:
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
"model": model,
|
|
365
|
+
"input_tokens": total_input,
|
|
366
|
+
"output_tokens": total_output,
|
|
367
|
+
"cache_read_tokens": total_cache_read,
|
|
368
|
+
"cache_write_tokens": total_cache_write,
|
|
369
|
+
"total_tokens": total_input + total_output,
|
|
370
|
+
}
|
|
371
|
+
except (OSError, Exception):
|
|
372
|
+
return None
|
|
373
|
+
|
|
374
|
+
|
|
317
375
|
def handle_stop(data: dict) -> None:
|
|
318
376
|
"""Finalize the current session with a summary."""
|
|
319
377
|
session_id = active_session()
|
|
@@ -343,13 +401,18 @@ def handle_stop(data: dict) -> None:
|
|
|
343
401
|
except json.JSONDecodeError:
|
|
344
402
|
pass
|
|
345
403
|
|
|
346
|
-
# Read start time from meta
|
|
404
|
+
# Read start time and transcript path from meta
|
|
347
405
|
meta_file = sdir / "meta.json"
|
|
348
406
|
start_time = None
|
|
407
|
+
transcript_path = None
|
|
349
408
|
if meta_file.exists():
|
|
350
409
|
try:
|
|
351
410
|
meta = json.loads(meta_file.read_text())
|
|
352
411
|
start_time = meta.get("start_time")
|
|
412
|
+
transcript_path = meta.get("transcript_path")
|
|
413
|
+
# Also check the Stop hook data for transcript_path
|
|
414
|
+
if not transcript_path:
|
|
415
|
+
transcript_path = data.get("transcript_path")
|
|
353
416
|
meta["status"] = "completed"
|
|
354
417
|
meta_file.write_text(json.dumps(meta, indent=2))
|
|
355
418
|
except (json.JSONDecodeError, OSError):
|
|
@@ -369,7 +432,7 @@ def handle_stop(data: dict) -> None:
|
|
|
369
432
|
prompts_file = sdir / "prompts.jsonl"
|
|
370
433
|
prompt_count = _count_lines(prompts_file)
|
|
371
434
|
|
|
372
|
-
summary = {
|
|
435
|
+
summary: dict = {
|
|
373
436
|
"end_time": end_time,
|
|
374
437
|
"duration_seconds": duration,
|
|
375
438
|
"prompts": prompt_count,
|
|
@@ -381,6 +444,12 @@ def handle_stop(data: dict) -> None:
|
|
|
381
444
|
"total_lines_changed": total_lines,
|
|
382
445
|
}
|
|
383
446
|
|
|
447
|
+
# Compute token usage from transcript
|
|
448
|
+
if transcript_path:
|
|
449
|
+
token_usage = _compute_token_usage(transcript_path)
|
|
450
|
+
if token_usage:
|
|
451
|
+
summary["tokens"] = token_usage
|
|
452
|
+
|
|
384
453
|
(sdir / "summary.json").write_text(json.dumps(summary, indent=2))
|
|
385
454
|
|
|
386
455
|
# Clear active session
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|