@pjmendonca/devflow 1.13.2 → 1.19.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.
Files changed (236) hide show
  1. package/.claude/commands/agent.md +1 -1
  2. package/.claude/commands/brainstorm.md +28 -0
  3. package/.claude/commands/bugfix.md +21 -0
  4. package/.claude/commands/checkpoint.md +0 -1
  5. package/.claude/commands/collab.md +0 -1
  6. package/.claude/commands/costs.md +88 -18
  7. package/.claude/commands/devflow.md +26 -0
  8. package/.claude/commands/handoff.md +0 -1
  9. package/.claude/commands/init.md +383 -0
  10. package/.claude/commands/memory.md +0 -1
  11. package/.claude/commands/pair.md +0 -1
  12. package/.claude/commands/review.md +27 -0
  13. package/.claude/commands/route.md +0 -1
  14. package/.claude/commands/swarm.md +0 -1
  15. package/.claude/commands/validate.md +55 -0
  16. package/.claude/hooks/session-notification.sh +44 -0
  17. package/.claude/hooks/session-startup.sh +427 -0
  18. package/.claude/hooks/session-stop.sh +38 -0
  19. package/.claude/hooks/session_tracker.py +272 -0
  20. package/.claude/settings.json +38 -0
  21. package/.claude/skills/brainstorm/SKILL.md +531 -0
  22. package/.claude/skills/costs/SKILL.md +156 -0
  23. package/.claude/skills/validate/SKILL.md +101 -0
  24. package/CHANGELOG.md +284 -0
  25. package/README.md +207 -10
  26. package/bin/devflow-install.js +2 -1
  27. package/bin/devflow.js +4 -0
  28. package/lib/constants.js +0 -1
  29. package/lib/exec-python.js +1 -1
  30. package/package.json +1 -1
  31. package/tooling/.automation/.checkpoint_lock +1 -0
  32. package/tooling/.automation/agents/architect.md +19 -0
  33. package/tooling/.automation/agents/ba.md +19 -0
  34. package/tooling/.automation/agents/maintainer.md +19 -0
  35. package/tooling/.automation/agents/pm.md +19 -0
  36. package/tooling/.automation/agents/reviewer.md +1 -1
  37. package/tooling/.automation/agents/writer.md +19 -0
  38. package/tooling/.automation/benchmarks/benchmark_20251230_100119.json +314 -0
  39. package/tooling/.automation/benchmarks/benchmark_20251230_100216.json +314 -0
  40. package/tooling/.automation/costs/config.json +31 -0
  41. package/tooling/.automation/costs/sessions/2025-12-29_20251229_164128.json +22 -0
  42. package/tooling/.automation/memory/knowledge/kg_integration-test.json +738 -1
  43. package/tooling/.automation/memory/knowledge/kg_test-story.json +3381 -2
  44. package/tooling/.automation/memory/shared/shared_integration-test.json +193 -1
  45. package/tooling/.automation/memory/shared/shared_test-story.json +757 -1
  46. package/tooling/.automation/memory/shared/shared_test.json +1332 -0
  47. package/tooling/.automation/memory/shared/shared_validation-check.json +240 -0
  48. package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +5 -5
  49. package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +23 -5
  50. package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +24 -6
  51. package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +4 -4
  52. package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +4 -4
  53. package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +4 -4
  54. package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +18 -0
  55. package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +19 -1
  56. package/tooling/.automation/overrides/templates/dev/security-focused.yaml +18 -0
  57. package/tooling/.automation/overrides/templates/dev/user-advocate.yaml +54 -0
  58. package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +4 -4
  59. package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +4 -4
  60. package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +4 -4
  61. package/tooling/.automation/overrides/templates/maintainer/reliability-engineer.yaml +55 -0
  62. package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +4 -4
  63. package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +3 -3
  64. package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +4 -4
  65. package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +18 -0
  66. package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +18 -0
  67. package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +2 -2
  68. package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +3 -3
  69. package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +5 -5
  70. package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +4 -4
  71. package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +5 -5
  72. package/tooling/.automation/validation/history/2025-12-29_val_002a28c1.json +32 -0
  73. package/tooling/.automation/validation/history/2025-12-29_val_01273bb1.json +32 -0
  74. package/tooling/.automation/validation/history/2025-12-29_val_03369914.json +41 -0
  75. package/tooling/.automation/validation/history/2025-12-29_val_07a449ba.json +32 -0
  76. package/tooling/.automation/validation/history/2025-12-29_val_0df1f0a2.json +41 -0
  77. package/tooling/.automation/validation/history/2025-12-29_val_10ff3d34.json +41 -0
  78. package/tooling/.automation/validation/history/2025-12-29_val_110771d7.json +32 -0
  79. package/tooling/.automation/validation/history/2025-12-29_val_13f3a7f9.json +32 -0
  80. package/tooling/.automation/validation/history/2025-12-29_val_17ba9d21.json +41 -0
  81. package/tooling/.automation/validation/history/2025-12-29_val_22247089.json +32 -0
  82. package/tooling/.automation/validation/history/2025-12-29_val_227ea6a4.json +32 -0
  83. package/tooling/.automation/validation/history/2025-12-29_val_2335d5ae.json +32 -0
  84. package/tooling/.automation/validation/history/2025-12-29_val_246824bb.json +41 -0
  85. package/tooling/.automation/validation/history/2025-12-29_val_28b4b9cd.json +32 -0
  86. package/tooling/.automation/validation/history/2025-12-29_val_2abd12cc.json +32 -0
  87. package/tooling/.automation/validation/history/2025-12-29_val_2c801b2f.json +59 -0
  88. package/tooling/.automation/validation/history/2025-12-29_val_2c8cfa8e.json +32 -0
  89. package/tooling/.automation/validation/history/2025-12-29_val_2ce76eb0.json +32 -0
  90. package/tooling/.automation/validation/history/2025-12-29_val_30351948.json +41 -0
  91. package/tooling/.automation/validation/history/2025-12-29_val_30eb7229.json +41 -0
  92. package/tooling/.automation/validation/history/2025-12-29_val_34df0e77.json +41 -0
  93. package/tooling/.automation/validation/history/2025-12-29_val_376e4d6a.json +32 -0
  94. package/tooling/.automation/validation/history/2025-12-29_val_3a4e8a1a.json +59 -0
  95. package/tooling/.automation/validation/history/2025-12-29_val_3b77a628.json +32 -0
  96. package/tooling/.automation/validation/history/2025-12-29_val_3ea4e1cf.json +59 -0
  97. package/tooling/.automation/validation/history/2025-12-29_val_44aacdb4.json +59 -0
  98. package/tooling/.automation/validation/history/2025-12-29_val_457ddfa8.json +32 -0
  99. package/tooling/.automation/validation/history/2025-12-29_val_45af6238.json +41 -0
  100. package/tooling/.automation/validation/history/2025-12-29_val_4735dba1.json +41 -0
  101. package/tooling/.automation/validation/history/2025-12-29_val_486b203c.json +41 -0
  102. package/tooling/.automation/validation/history/2025-12-29_val_49dc56cd.json +59 -0
  103. package/tooling/.automation/validation/history/2025-12-29_val_4d863d6d.json +32 -0
  104. package/tooling/.automation/validation/history/2025-12-29_val_5149a808.json +59 -0
  105. package/tooling/.automation/validation/history/2025-12-29_val_52e0bb43.json +32 -0
  106. package/tooling/.automation/validation/history/2025-12-29_val_585d6319.json +59 -0
  107. package/tooling/.automation/validation/history/2025-12-29_val_5b2d859a.json +32 -0
  108. package/tooling/.automation/validation/history/2025-12-29_val_635a7081.json +41 -0
  109. package/tooling/.automation/validation/history/2025-12-29_val_64df4905.json +32 -0
  110. package/tooling/.automation/validation/history/2025-12-29_val_70634cee.json +41 -0
  111. package/tooling/.automation/validation/history/2025-12-29_val_714553f9.json +32 -0
  112. package/tooling/.automation/validation/history/2025-12-29_val_7f7bfdbf.json +41 -0
  113. package/tooling/.automation/validation/history/2025-12-29_val_7faad91d.json +32 -0
  114. package/tooling/.automation/validation/history/2025-12-29_val_81821f8f.json +41 -0
  115. package/tooling/.automation/validation/history/2025-12-29_val_8249f3c9.json +32 -0
  116. package/tooling/.automation/validation/history/2025-12-29_val_8422b50f.json +41 -0
  117. package/tooling/.automation/validation/history/2025-12-29_val_8446c134.json +32 -0
  118. package/tooling/.automation/validation/history/2025-12-29_val_879f4e26.json +59 -0
  119. package/tooling/.automation/validation/history/2025-12-29_val_8b6d5bd7.json +32 -0
  120. package/tooling/.automation/validation/history/2025-12-29_val_8c5cd787.json +32 -0
  121. package/tooling/.automation/validation/history/2025-12-29_val_91d20bc7.json +32 -0
  122. package/tooling/.automation/validation/history/2025-12-29_val_958a12b7.json +41 -0
  123. package/tooling/.automation/validation/history/2025-12-29_val_95d91108.json +41 -0
  124. package/tooling/.automation/validation/history/2025-12-29_val_980dbb74.json +32 -0
  125. package/tooling/.automation/validation/history/2025-12-29_val_9e40c79b.json +32 -0
  126. package/tooling/.automation/validation/history/2025-12-29_val_9f499b7c.json +32 -0
  127. package/tooling/.automation/validation/history/2025-12-29_val_9f7c3b57.json +32 -0
  128. package/tooling/.automation/validation/history/2025-12-29_val_a30d5bd4.json +32 -0
  129. package/tooling/.automation/validation/history/2025-12-29_val_a6eb09c7.json +32 -0
  130. package/tooling/.automation/validation/history/2025-12-29_val_a86f7b83.json +41 -0
  131. package/tooling/.automation/validation/history/2025-12-29_val_ad5347e1.json +41 -0
  132. package/tooling/.automation/validation/history/2025-12-29_val_b0a5a993.json +32 -0
  133. package/tooling/.automation/validation/history/2025-12-29_val_bcb0192e.json +32 -0
  134. package/tooling/.automation/validation/history/2025-12-29_val_bf3c9aaa.json +32 -0
  135. package/tooling/.automation/validation/history/2025-12-29_val_c461ff88.json +32 -0
  136. package/tooling/.automation/validation/history/2025-12-29_val_c4f4e258.json +41 -0
  137. package/tooling/.automation/validation/history/2025-12-29_val_c7f0fa6d.json +41 -0
  138. package/tooling/.automation/validation/history/2025-12-29_val_c911b0e6.json +32 -0
  139. package/tooling/.automation/validation/history/2025-12-29_val_cc581964.json +32 -0
  140. package/tooling/.automation/validation/history/2025-12-29_val_cdd5a33b.json +32 -0
  141. package/tooling/.automation/validation/history/2025-12-29_val_cfd42495.json +32 -0
  142. package/tooling/.automation/validation/history/2025-12-29_val_d1c7a4ee.json +41 -0
  143. package/tooling/.automation/validation/history/2025-12-29_val_d2280d0e.json +32 -0
  144. package/tooling/.automation/validation/history/2025-12-29_val_d2a6ff69.json +32 -0
  145. package/tooling/.automation/validation/history/2025-12-29_val_d8c53ab2.json +59 -0
  146. package/tooling/.automation/validation/history/2025-12-29_val_d9c1247a.json +41 -0
  147. package/tooling/.automation/validation/history/2025-12-29_val_d9d58569.json +32 -0
  148. package/tooling/.automation/validation/history/2025-12-29_val_dabb4fd9.json +32 -0
  149. package/tooling/.automation/validation/history/2025-12-29_val_dd8fe359.json +32 -0
  150. package/tooling/.automation/validation/history/2025-12-29_val_decdffc9.json +32 -0
  151. package/tooling/.automation/validation/history/2025-12-29_val_e3a95476.json +59 -0
  152. package/tooling/.automation/validation/history/2025-12-29_val_e776dfca.json +32 -0
  153. package/tooling/.automation/validation/history/2025-12-29_val_ea70969f.json +59 -0
  154. package/tooling/.automation/validation/history/2025-12-29_val_ef41ea95.json +32 -0
  155. package/tooling/.automation/validation/history/2025-12-29_val_f384f9b1.json +32 -0
  156. package/tooling/.automation/validation/history/2025-12-29_val_f8adc38c.json +41 -0
  157. package/tooling/.automation/validation/history/2025-12-29_val_fa40b69e.json +32 -0
  158. package/tooling/.automation/validation/history/2025-12-29_val_fc538d54.json +41 -0
  159. package/tooling/.automation/validation/history/2025-12-29_val_fe814665.json +32 -0
  160. package/tooling/.automation/validation/history/2025-12-29_val_ffea4b12.json +32 -0
  161. package/tooling/.automation/validation/history/2025-12-30_val_02d001e5.json +59 -0
  162. package/tooling/.automation/validation/history/2025-12-30_val_0b8966dc.json +32 -0
  163. package/tooling/.automation/validation/history/2025-12-30_val_15455fbf.json +59 -0
  164. package/tooling/.automation/validation/history/2025-12-30_val_157e34b9.json +32 -0
  165. package/tooling/.automation/validation/history/2025-12-30_val_28d1d933.json +32 -0
  166. package/tooling/.automation/validation/history/2025-12-30_val_3442a52c.json +32 -0
  167. package/tooling/.automation/validation/history/2025-12-30_val_37f1ce1e.json +32 -0
  168. package/tooling/.automation/validation/history/2025-12-30_val_4f1d8a93.json +32 -0
  169. package/tooling/.automation/validation/history/2025-12-30_val_56ff1de3.json +32 -0
  170. package/tooling/.automation/validation/history/2025-12-30_val_664fd4e2.json +41 -0
  171. package/tooling/.automation/validation/history/2025-12-30_val_66afb0a7.json +32 -0
  172. package/tooling/.automation/validation/history/2025-12-30_val_7634663c.json +41 -0
  173. package/tooling/.automation/validation/history/2025-12-30_val_8ea830c3.json +41 -0
  174. package/tooling/.automation/validation/history/2025-12-30_val_998957c2.json +32 -0
  175. package/tooling/.automation/validation/history/2025-12-30_val_a52177db.json +32 -0
  176. package/tooling/.automation/validation/history/2025-12-30_val_a5b65a63.json +32 -0
  177. package/tooling/.automation/validation/history/2025-12-30_val_ae391d0e.json +32 -0
  178. package/tooling/.automation/validation/history/2025-12-30_val_c7895339.json +41 -0
  179. package/tooling/.automation/validation/history/2025-12-30_val_ca416593.json +41 -0
  180. package/tooling/.automation/validation/history/2025-12-30_val_cee19422.json +32 -0
  181. package/tooling/.automation/validation/history/2025-12-30_val_ddd4f4e6.json +32 -0
  182. package/tooling/.automation/validation/history/2025-12-30_val_f2e1394b.json +32 -0
  183. package/tooling/.automation/validation/history/2025-12-30_val_f4a7fa06.json +41 -0
  184. package/tooling/.automation/validation/history/2025-12-30_val_ffea3369.json +32 -0
  185. package/tooling/.automation/validation/history/2026-01-03_val_1287a74c.json +41 -0
  186. package/tooling/.automation/validation/history/2026-01-03_val_3b24071f.json +32 -0
  187. package/tooling/.automation/validation/history/2026-01-03_val_44d77573.json +32 -0
  188. package/tooling/.automation/validation/history/2026-01-03_val_5b31dc51.json +32 -0
  189. package/tooling/.automation/validation/history/2026-01-03_val_74267244.json +32 -0
  190. package/tooling/.automation/validation/history/2026-01-03_val_8b2d95c7.json +59 -0
  191. package/tooling/.automation/validation/history/2026-01-03_val_d875b297.json +41 -0
  192. package/tooling/.automation/validation-config.yaml +103 -0
  193. package/tooling/completions/DevflowCompletion.ps1 +21 -21
  194. package/tooling/completions/_run-story +3 -3
  195. package/tooling/completions/run-story-completion.bash +8 -8
  196. package/tooling/docs/DOC-STANDARD.md +14 -14
  197. package/tooling/docs/stories/.gitkeep +0 -0
  198. package/tooling/docs/templates/brainstorm-guide.md +314 -0
  199. package/tooling/docs/templates/migration-spec.md +4 -4
  200. package/tooling/docs/templates/story.md +66 -0
  201. package/tooling/scripts/context_checkpoint.py +5 -15
  202. package/tooling/scripts/cost_dashboard.py +610 -13
  203. package/tooling/scripts/create-persona.py +1 -12
  204. package/tooling/scripts/create-persona.sh +44 -44
  205. package/tooling/scripts/lib/__init__.py +12 -1
  206. package/tooling/scripts/lib/agent_handoff.py +11 -2
  207. package/tooling/scripts/lib/agent_router.py +31 -10
  208. package/tooling/scripts/lib/colors.py +106 -0
  209. package/tooling/scripts/lib/context_monitor.py +766 -0
  210. package/tooling/scripts/lib/cost_config.py +229 -10
  211. package/tooling/scripts/lib/cost_display.py +20 -45
  212. package/tooling/scripts/lib/cost_tracker.py +462 -15
  213. package/tooling/scripts/lib/currency_converter.py +28 -5
  214. package/tooling/scripts/lib/pair_programming.py +102 -3
  215. package/tooling/scripts/lib/personality_system.py +949 -0
  216. package/tooling/scripts/lib/platform.py +55 -0
  217. package/tooling/scripts/lib/shared_memory.py +9 -3
  218. package/tooling/scripts/lib/swarm_orchestrator.py +514 -75
  219. package/tooling/scripts/lib/validation_loop.py +1014 -0
  220. package/tooling/scripts/memory_summarize.py +9 -2
  221. package/tooling/scripts/new-doc.py +2 -9
  222. package/tooling/scripts/personalize_agent.py +1 -12
  223. package/tooling/scripts/rollback-migration.sh +60 -60
  224. package/tooling/scripts/run-collab.ps1 +16 -16
  225. package/tooling/scripts/run-collab.py +88 -53
  226. package/tooling/scripts/run-collab.sh +4 -4
  227. package/tooling/scripts/run-story.py +278 -20
  228. package/tooling/scripts/run-story.sh +3 -3
  229. package/tooling/scripts/setup-checkpoint-service.py +2 -9
  230. package/tooling/scripts/tech-debt-tracker.py +1 -12
  231. package/tooling/scripts/test_adversarial_swarm.py +452 -0
  232. package/tooling/scripts/validate-overrides.py +1 -10
  233. package/tooling/scripts/validate-overrides.sh +40 -40
  234. package/tooling/scripts/validate_loop.py +162 -0
  235. package/tooling/scripts/validate_setup.py +2 -30
  236. package/.claude/skills/init/SKILL.md +0 -496
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Session Tracker - Tracks costs for ALL Claude Code sessions.
4
+
5
+ Called by hooks to track token usage across the entire session lifecycle.
6
+
7
+ Usage:
8
+ python session_tracker.py start [--session-id ID]
9
+ python session_tracker.py log --input TOKENS --output TOKENS [--model MODEL]
10
+ python session_tracker.py end
11
+ python session_tracker.py status
12
+ """
13
+
14
+ import argparse
15
+ import json
16
+ from datetime import datetime
17
+ from pathlib import Path
18
+
19
+ # Project paths
20
+ PROJECT_DIR = Path(__file__).parent.parent.parent
21
+ COSTS_DIR = PROJECT_DIR / "tooling" / ".automation" / "costs"
22
+ SESSIONS_DIR = COSTS_DIR / "sessions"
23
+ ACTIVE_SESSION_FILE = COSTS_DIR / ".active_session.json"
24
+
25
+ # Pricing per 1M tokens (USD) - December 2025
26
+ PRICING = {
27
+ "claude-opus-4-5-20251101": {"input": 15.00, "output": 75.00},
28
+ "claude-sonnet-4-20250514": {"input": 3.00, "output": 15.00},
29
+ "claude-3-5-sonnet-20241022": {"input": 3.00, "output": 15.00},
30
+ "claude-3-5-haiku-20241022": {"input": 0.80, "output": 4.00},
31
+ "claude-3-opus-20240229": {"input": 15.00, "output": 75.00},
32
+ "opus": {"input": 15.00, "output": 75.00},
33
+ "sonnet": {"input": 3.00, "output": 15.00},
34
+ "haiku": {"input": 0.80, "output": 4.00},
35
+ }
36
+
37
+
38
+ def get_pricing(model: str) -> dict:
39
+ """Get pricing for a model."""
40
+ model_lower = model.lower()
41
+
42
+ # Direct match
43
+ if model_lower in PRICING:
44
+ return PRICING[model_lower]
45
+
46
+ # Partial match
47
+ for key, price in PRICING.items():
48
+ if key in model_lower or model_lower in key:
49
+ return price
50
+
51
+ # Check for model family
52
+ if "opus" in model_lower:
53
+ return PRICING["opus"]
54
+ elif "sonnet" in model_lower:
55
+ return PRICING["sonnet"]
56
+ elif "haiku" in model_lower:
57
+ return PRICING["haiku"]
58
+
59
+ # Default to sonnet pricing
60
+ return PRICING["sonnet"]
61
+
62
+
63
+ def calculate_cost(input_tokens: int, output_tokens: int, model: str) -> float:
64
+ """Calculate cost in USD."""
65
+ pricing = get_pricing(model)
66
+ input_cost = (input_tokens / 1_000_000) * pricing["input"]
67
+ output_cost = (output_tokens / 1_000_000) * pricing["output"]
68
+ return input_cost + output_cost
69
+
70
+
71
+ def ensure_dirs():
72
+ """Ensure required directories exist."""
73
+ COSTS_DIR.mkdir(parents=True, exist_ok=True)
74
+ SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
75
+
76
+
77
+ def start_session(session_id: str = None, model: str = None) -> dict:
78
+ """Start a new tracking session."""
79
+ ensure_dirs()
80
+
81
+ now = datetime.now()
82
+ if not session_id:
83
+ session_id = now.strftime("%Y%m%d_%H%M%S")
84
+
85
+ session = {
86
+ "session_id": session_id,
87
+ "start_time": now.isoformat(),
88
+ "end_time": None,
89
+ "model": model or "unknown",
90
+ "story_key": None,
91
+ "entries": [],
92
+ "totals": {
93
+ "input_tokens": 0,
94
+ "output_tokens": 0,
95
+ "total_tokens": 0,
96
+ "cost_usd": 0.0,
97
+ },
98
+ }
99
+
100
+ # Save active session
101
+ with open(ACTIVE_SESSION_FILE, "w") as f:
102
+ json.dump(session, f, indent=2)
103
+
104
+ return session
105
+
106
+
107
+ def get_active_session() -> dict | None:
108
+ """Get the currently active session."""
109
+ if not ACTIVE_SESSION_FILE.exists():
110
+ return None
111
+
112
+ try:
113
+ with open(ACTIVE_SESSION_FILE) as f:
114
+ return json.load(f)
115
+ except (OSError, json.JSONDecodeError):
116
+ return None
117
+
118
+
119
+ def log_usage(input_tokens: int, output_tokens: int, model: str = None) -> dict | None:
120
+ """Log token usage to the active session."""
121
+ session = get_active_session()
122
+
123
+ if not session:
124
+ # Auto-start session if none exists
125
+ session = start_session(model=model)
126
+
127
+ # Update model if provided
128
+ if model and session.get("model") == "unknown":
129
+ session["model"] = model
130
+
131
+ # Use session model for pricing
132
+ use_model = model or session.get("model", "sonnet")
133
+ cost = calculate_cost(input_tokens, output_tokens, use_model)
134
+
135
+ # Create entry
136
+ entry = {
137
+ "timestamp": datetime.now().isoformat(),
138
+ "model": use_model,
139
+ "input_tokens": input_tokens,
140
+ "output_tokens": output_tokens,
141
+ "cost_usd": cost,
142
+ }
143
+
144
+ session["entries"].append(entry)
145
+
146
+ # Update totals
147
+ session["totals"]["input_tokens"] += input_tokens
148
+ session["totals"]["output_tokens"] += output_tokens
149
+ session["totals"]["total_tokens"] += input_tokens + output_tokens
150
+ session["totals"]["cost_usd"] += cost
151
+
152
+ # Save updated session
153
+ with open(ACTIVE_SESSION_FILE, "w") as f:
154
+ json.dump(session, f, indent=2)
155
+
156
+ return session
157
+
158
+
159
+ def end_session() -> dict | None:
160
+ """End the current session and save to sessions directory."""
161
+ session = get_active_session()
162
+
163
+ if not session:
164
+ return None
165
+
166
+ # Set end time
167
+ session["end_time"] = datetime.now().isoformat()
168
+
169
+ # Only save if there was activity
170
+ if session["totals"]["total_tokens"] > 0:
171
+ # Generate filename
172
+ start_time = datetime.fromisoformat(session["start_time"])
173
+ filename = f"{start_time.strftime('%Y-%m-%d')}_{session['session_id']}.json"
174
+ session_file = SESSIONS_DIR / filename
175
+
176
+ # Save to sessions directory
177
+ with open(session_file, "w") as f:
178
+ json.dump(session, f, indent=2)
179
+
180
+ # Remove active session file
181
+ if ACTIVE_SESSION_FILE.exists():
182
+ ACTIVE_SESSION_FILE.unlink()
183
+
184
+ return session
185
+
186
+
187
+ def get_status() -> dict:
188
+ """Get current session status."""
189
+ session = get_active_session()
190
+
191
+ if not session:
192
+ return {"active": False, "message": "No active session"}
193
+
194
+ totals = session.get("totals", {})
195
+ return {
196
+ "active": True,
197
+ "session_id": session.get("session_id"),
198
+ "model": session.get("model"),
199
+ "input_tokens": totals.get("input_tokens", 0),
200
+ "output_tokens": totals.get("output_tokens", 0),
201
+ "total_tokens": totals.get("total_tokens", 0),
202
+ "cost_usd": totals.get("cost_usd", 0.0),
203
+ "entries_count": len(session.get("entries", [])),
204
+ }
205
+
206
+
207
+ def main():
208
+ parser = argparse.ArgumentParser(description="Session cost tracker")
209
+ subparsers = parser.add_subparsers(dest="command", required=True)
210
+
211
+ # Start command
212
+ start_parser = subparsers.add_parser("start", help="Start a new session")
213
+ start_parser.add_argument("--session-id", help="Custom session ID")
214
+ start_parser.add_argument("--model", help="Model name")
215
+
216
+ # Log command
217
+ log_parser = subparsers.add_parser("log", help="Log token usage")
218
+ log_parser.add_argument("--input", type=int, required=True, help="Input tokens")
219
+ log_parser.add_argument("--output", type=int, required=True, help="Output tokens")
220
+ log_parser.add_argument("--model", help="Model name")
221
+
222
+ # End command
223
+ subparsers.add_parser("end", help="End current session")
224
+
225
+ # Status command
226
+ subparsers.add_parser("status", help="Get session status")
227
+
228
+ args = parser.parse_args()
229
+
230
+ if args.command == "start":
231
+ session = start_session(args.session_id, args.model)
232
+ print(json.dumps({"status": "started", "session_id": session["session_id"]}))
233
+
234
+ elif args.command == "log":
235
+ model = getattr(args, "model", None)
236
+ session = log_usage(args.input, args.output, model)
237
+ if session:
238
+ print(
239
+ json.dumps(
240
+ {
241
+ "status": "logged",
242
+ "total_tokens": session["totals"]["total_tokens"],
243
+ "cost_usd": round(session["totals"]["cost_usd"], 4),
244
+ }
245
+ )
246
+ )
247
+ else:
248
+ print(json.dumps({"status": "error", "message": "No active session"}))
249
+
250
+ elif args.command == "end":
251
+ session = end_session()
252
+ if session:
253
+ print(
254
+ json.dumps(
255
+ {
256
+ "status": "ended",
257
+ "total_tokens": session["totals"]["total_tokens"],
258
+ "cost_usd": round(session["totals"]["cost_usd"], 4),
259
+ "saved": session["totals"]["total_tokens"] > 0,
260
+ }
261
+ )
262
+ )
263
+ else:
264
+ print(json.dumps({"status": "no_session"}))
265
+
266
+ elif args.command == "status":
267
+ status = get_status()
268
+ print(json.dumps(status))
269
+
270
+
271
+ if __name__ == "__main__":
272
+ main()
@@ -0,0 +1,38 @@
1
+ {
2
+ "statusLine": {
3
+ "type": "command",
4
+ "command": "~/.claude/statusline.sh"
5
+ },
6
+ "hooks": {
7
+ "SessionStart": [
8
+ {
9
+ "hooks": [
10
+ {
11
+ "type": "command",
12
+ "command": ".claude/hooks/session-startup.sh"
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ "Stop": [
18
+ {
19
+ "hooks": [
20
+ {
21
+ "type": "command",
22
+ "command": ".claude/hooks/session-stop.sh"
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "Notification": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": ".claude/hooks/session-notification.sh"
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
38
+ }