@pjmendonca/devflow 1.13.2 → 1.18.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 (224) hide show
  1. package/.claude/commands/agent.md +1 -1
  2. package/.claude/commands/bugfix.md +21 -0
  3. package/.claude/commands/checkpoint.md +0 -1
  4. package/.claude/commands/collab.md +0 -1
  5. package/.claude/commands/costs.md +88 -18
  6. package/.claude/commands/devflow.md +26 -0
  7. package/.claude/commands/handoff.md +0 -1
  8. package/.claude/commands/init.md +287 -0
  9. package/.claude/commands/memory.md +0 -1
  10. package/.claude/commands/pair.md +0 -1
  11. package/.claude/commands/review.md +27 -0
  12. package/.claude/commands/route.md +0 -1
  13. package/.claude/commands/swarm.md +0 -1
  14. package/.claude/commands/validate.md +55 -0
  15. package/.claude/hooks/session-notification.sh +44 -0
  16. package/.claude/hooks/session-startup.sh +427 -0
  17. package/.claude/hooks/session-stop.sh +38 -0
  18. package/.claude/hooks/session_tracker.py +272 -0
  19. package/.claude/settings.json +38 -0
  20. package/.claude/skills/costs/SKILL.md +156 -0
  21. package/.claude/skills/validate/SKILL.md +101 -0
  22. package/CHANGELOG.md +243 -0
  23. package/README.md +207 -10
  24. package/bin/devflow-install.js +2 -1
  25. package/bin/devflow.js +4 -0
  26. package/lib/constants.js +0 -1
  27. package/lib/exec-python.js +1 -1
  28. package/package.json +1 -1
  29. package/tooling/.automation/.checkpoint_lock +1 -0
  30. package/tooling/.automation/agents/architect.md +19 -0
  31. package/tooling/.automation/agents/ba.md +19 -0
  32. package/tooling/.automation/agents/maintainer.md +19 -0
  33. package/tooling/.automation/agents/pm.md +19 -0
  34. package/tooling/.automation/agents/reviewer.md +1 -1
  35. package/tooling/.automation/agents/writer.md +19 -0
  36. package/tooling/.automation/benchmarks/benchmark_20251230_100119.json +314 -0
  37. package/tooling/.automation/benchmarks/benchmark_20251230_100216.json +314 -0
  38. package/tooling/.automation/costs/config.json +31 -0
  39. package/tooling/.automation/costs/sessions/2025-12-29_20251229_164128.json +22 -0
  40. package/tooling/.automation/memory/knowledge/kg_integration-test.json +707 -1
  41. package/tooling/.automation/memory/knowledge/kg_test-story.json +3273 -2
  42. package/tooling/.automation/memory/shared/shared_integration-test.json +181 -1
  43. package/tooling/.automation/memory/shared/shared_test-story.json +721 -1
  44. package/tooling/.automation/memory/shared/shared_test.json +1254 -0
  45. package/tooling/.automation/memory/shared/shared_validation-check.json +227 -0
  46. package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +5 -5
  47. package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +23 -5
  48. package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +24 -6
  49. package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +4 -4
  50. package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +4 -4
  51. package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +4 -4
  52. package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +18 -0
  53. package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +19 -1
  54. package/tooling/.automation/overrides/templates/dev/security-focused.yaml +18 -0
  55. package/tooling/.automation/overrides/templates/dev/user-advocate.yaml +54 -0
  56. package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +4 -4
  57. package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +4 -4
  58. package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +4 -4
  59. package/tooling/.automation/overrides/templates/maintainer/reliability-engineer.yaml +55 -0
  60. package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +4 -4
  61. package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +3 -3
  62. package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +4 -4
  63. package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +18 -0
  64. package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +18 -0
  65. package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +2 -2
  66. package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +3 -3
  67. package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +5 -5
  68. package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +4 -4
  69. package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +5 -5
  70. package/tooling/.automation/validation/history/2025-12-29_val_002a28c1.json +32 -0
  71. package/tooling/.automation/validation/history/2025-12-29_val_01273bb1.json +32 -0
  72. package/tooling/.automation/validation/history/2025-12-29_val_03369914.json +41 -0
  73. package/tooling/.automation/validation/history/2025-12-29_val_07a449ba.json +32 -0
  74. package/tooling/.automation/validation/history/2025-12-29_val_0df1f0a2.json +41 -0
  75. package/tooling/.automation/validation/history/2025-12-29_val_10ff3d34.json +41 -0
  76. package/tooling/.automation/validation/history/2025-12-29_val_110771d7.json +32 -0
  77. package/tooling/.automation/validation/history/2025-12-29_val_13f3a7f9.json +32 -0
  78. package/tooling/.automation/validation/history/2025-12-29_val_17ba9d21.json +41 -0
  79. package/tooling/.automation/validation/history/2025-12-29_val_22247089.json +32 -0
  80. package/tooling/.automation/validation/history/2025-12-29_val_227ea6a4.json +32 -0
  81. package/tooling/.automation/validation/history/2025-12-29_val_2335d5ae.json +32 -0
  82. package/tooling/.automation/validation/history/2025-12-29_val_246824bb.json +41 -0
  83. package/tooling/.automation/validation/history/2025-12-29_val_28b4b9cd.json +32 -0
  84. package/tooling/.automation/validation/history/2025-12-29_val_2abd12cc.json +32 -0
  85. package/tooling/.automation/validation/history/2025-12-29_val_2c801b2f.json +59 -0
  86. package/tooling/.automation/validation/history/2025-12-29_val_2c8cfa8e.json +32 -0
  87. package/tooling/.automation/validation/history/2025-12-29_val_2ce76eb0.json +32 -0
  88. package/tooling/.automation/validation/history/2025-12-29_val_30351948.json +41 -0
  89. package/tooling/.automation/validation/history/2025-12-29_val_30eb7229.json +41 -0
  90. package/tooling/.automation/validation/history/2025-12-29_val_34df0e77.json +41 -0
  91. package/tooling/.automation/validation/history/2025-12-29_val_376e4d6a.json +32 -0
  92. package/tooling/.automation/validation/history/2025-12-29_val_3a4e8a1a.json +59 -0
  93. package/tooling/.automation/validation/history/2025-12-29_val_3b77a628.json +32 -0
  94. package/tooling/.automation/validation/history/2025-12-29_val_3ea4e1cf.json +59 -0
  95. package/tooling/.automation/validation/history/2025-12-29_val_44aacdb4.json +59 -0
  96. package/tooling/.automation/validation/history/2025-12-29_val_457ddfa8.json +32 -0
  97. package/tooling/.automation/validation/history/2025-12-29_val_45af6238.json +41 -0
  98. package/tooling/.automation/validation/history/2025-12-29_val_4735dba1.json +41 -0
  99. package/tooling/.automation/validation/history/2025-12-29_val_486b203c.json +41 -0
  100. package/tooling/.automation/validation/history/2025-12-29_val_49dc56cd.json +59 -0
  101. package/tooling/.automation/validation/history/2025-12-29_val_4d863d6d.json +32 -0
  102. package/tooling/.automation/validation/history/2025-12-29_val_5149a808.json +59 -0
  103. package/tooling/.automation/validation/history/2025-12-29_val_52e0bb43.json +32 -0
  104. package/tooling/.automation/validation/history/2025-12-29_val_585d6319.json +59 -0
  105. package/tooling/.automation/validation/history/2025-12-29_val_5b2d859a.json +32 -0
  106. package/tooling/.automation/validation/history/2025-12-29_val_635a7081.json +41 -0
  107. package/tooling/.automation/validation/history/2025-12-29_val_64df4905.json +32 -0
  108. package/tooling/.automation/validation/history/2025-12-29_val_70634cee.json +41 -0
  109. package/tooling/.automation/validation/history/2025-12-29_val_714553f9.json +32 -0
  110. package/tooling/.automation/validation/history/2025-12-29_val_7f7bfdbf.json +41 -0
  111. package/tooling/.automation/validation/history/2025-12-29_val_7faad91d.json +32 -0
  112. package/tooling/.automation/validation/history/2025-12-29_val_81821f8f.json +41 -0
  113. package/tooling/.automation/validation/history/2025-12-29_val_8249f3c9.json +32 -0
  114. package/tooling/.automation/validation/history/2025-12-29_val_8422b50f.json +41 -0
  115. package/tooling/.automation/validation/history/2025-12-29_val_8446c134.json +32 -0
  116. package/tooling/.automation/validation/history/2025-12-29_val_879f4e26.json +59 -0
  117. package/tooling/.automation/validation/history/2025-12-29_val_8b6d5bd7.json +32 -0
  118. package/tooling/.automation/validation/history/2025-12-29_val_8c5cd787.json +32 -0
  119. package/tooling/.automation/validation/history/2025-12-29_val_91d20bc7.json +32 -0
  120. package/tooling/.automation/validation/history/2025-12-29_val_958a12b7.json +41 -0
  121. package/tooling/.automation/validation/history/2025-12-29_val_95d91108.json +41 -0
  122. package/tooling/.automation/validation/history/2025-12-29_val_980dbb74.json +32 -0
  123. package/tooling/.automation/validation/history/2025-12-29_val_9e40c79b.json +32 -0
  124. package/tooling/.automation/validation/history/2025-12-29_val_9f499b7c.json +32 -0
  125. package/tooling/.automation/validation/history/2025-12-29_val_9f7c3b57.json +32 -0
  126. package/tooling/.automation/validation/history/2025-12-29_val_a30d5bd4.json +32 -0
  127. package/tooling/.automation/validation/history/2025-12-29_val_a6eb09c7.json +32 -0
  128. package/tooling/.automation/validation/history/2025-12-29_val_a86f7b83.json +41 -0
  129. package/tooling/.automation/validation/history/2025-12-29_val_ad5347e1.json +41 -0
  130. package/tooling/.automation/validation/history/2025-12-29_val_b0a5a993.json +32 -0
  131. package/tooling/.automation/validation/history/2025-12-29_val_bcb0192e.json +32 -0
  132. package/tooling/.automation/validation/history/2025-12-29_val_bf3c9aaa.json +32 -0
  133. package/tooling/.automation/validation/history/2025-12-29_val_c461ff88.json +32 -0
  134. package/tooling/.automation/validation/history/2025-12-29_val_c4f4e258.json +41 -0
  135. package/tooling/.automation/validation/history/2025-12-29_val_c7f0fa6d.json +41 -0
  136. package/tooling/.automation/validation/history/2025-12-29_val_c911b0e6.json +32 -0
  137. package/tooling/.automation/validation/history/2025-12-29_val_cc581964.json +32 -0
  138. package/tooling/.automation/validation/history/2025-12-29_val_cdd5a33b.json +32 -0
  139. package/tooling/.automation/validation/history/2025-12-29_val_cfd42495.json +32 -0
  140. package/tooling/.automation/validation/history/2025-12-29_val_d1c7a4ee.json +41 -0
  141. package/tooling/.automation/validation/history/2025-12-29_val_d2280d0e.json +32 -0
  142. package/tooling/.automation/validation/history/2025-12-29_val_d2a6ff69.json +32 -0
  143. package/tooling/.automation/validation/history/2025-12-29_val_d8c53ab2.json +59 -0
  144. package/tooling/.automation/validation/history/2025-12-29_val_d9c1247a.json +41 -0
  145. package/tooling/.automation/validation/history/2025-12-29_val_d9d58569.json +32 -0
  146. package/tooling/.automation/validation/history/2025-12-29_val_dabb4fd9.json +32 -0
  147. package/tooling/.automation/validation/history/2025-12-29_val_dd8fe359.json +32 -0
  148. package/tooling/.automation/validation/history/2025-12-29_val_decdffc9.json +32 -0
  149. package/tooling/.automation/validation/history/2025-12-29_val_e3a95476.json +59 -0
  150. package/tooling/.automation/validation/history/2025-12-29_val_e776dfca.json +32 -0
  151. package/tooling/.automation/validation/history/2025-12-29_val_ea70969f.json +59 -0
  152. package/tooling/.automation/validation/history/2025-12-29_val_ef41ea95.json +32 -0
  153. package/tooling/.automation/validation/history/2025-12-29_val_f384f9b1.json +32 -0
  154. package/tooling/.automation/validation/history/2025-12-29_val_f8adc38c.json +41 -0
  155. package/tooling/.automation/validation/history/2025-12-29_val_fa40b69e.json +32 -0
  156. package/tooling/.automation/validation/history/2025-12-29_val_fc538d54.json +41 -0
  157. package/tooling/.automation/validation/history/2025-12-29_val_fe814665.json +32 -0
  158. package/tooling/.automation/validation/history/2025-12-29_val_ffea4b12.json +32 -0
  159. package/tooling/.automation/validation/history/2025-12-30_val_02d001e5.json +59 -0
  160. package/tooling/.automation/validation/history/2025-12-30_val_0b8966dc.json +32 -0
  161. package/tooling/.automation/validation/history/2025-12-30_val_15455fbf.json +59 -0
  162. package/tooling/.automation/validation/history/2025-12-30_val_157e34b9.json +32 -0
  163. package/tooling/.automation/validation/history/2025-12-30_val_28d1d933.json +32 -0
  164. package/tooling/.automation/validation/history/2025-12-30_val_3442a52c.json +32 -0
  165. package/tooling/.automation/validation/history/2025-12-30_val_37f1ce1e.json +32 -0
  166. package/tooling/.automation/validation/history/2025-12-30_val_4f1d8a93.json +32 -0
  167. package/tooling/.automation/validation/history/2025-12-30_val_56ff1de3.json +32 -0
  168. package/tooling/.automation/validation/history/2025-12-30_val_664fd4e2.json +41 -0
  169. package/tooling/.automation/validation/history/2025-12-30_val_66afb0a7.json +32 -0
  170. package/tooling/.automation/validation/history/2025-12-30_val_7634663c.json +41 -0
  171. package/tooling/.automation/validation/history/2025-12-30_val_8ea830c3.json +41 -0
  172. package/tooling/.automation/validation/history/2025-12-30_val_998957c2.json +32 -0
  173. package/tooling/.automation/validation/history/2025-12-30_val_a52177db.json +32 -0
  174. package/tooling/.automation/validation/history/2025-12-30_val_a5b65a63.json +32 -0
  175. package/tooling/.automation/validation/history/2025-12-30_val_ae391d0e.json +32 -0
  176. package/tooling/.automation/validation/history/2025-12-30_val_c7895339.json +41 -0
  177. package/tooling/.automation/validation/history/2025-12-30_val_ca416593.json +41 -0
  178. package/tooling/.automation/validation/history/2025-12-30_val_cee19422.json +32 -0
  179. package/tooling/.automation/validation/history/2025-12-30_val_ddd4f4e6.json +32 -0
  180. package/tooling/.automation/validation/history/2025-12-30_val_f2e1394b.json +32 -0
  181. package/tooling/.automation/validation/history/2025-12-30_val_f4a7fa06.json +41 -0
  182. package/tooling/.automation/validation/history/2025-12-30_val_ffea3369.json +32 -0
  183. package/tooling/.automation/validation-config.yaml +103 -0
  184. package/tooling/completions/DevflowCompletion.ps1 +21 -21
  185. package/tooling/completions/_run-story +3 -3
  186. package/tooling/completions/run-story-completion.bash +8 -8
  187. package/tooling/docs/DOC-STANDARD.md +14 -14
  188. package/tooling/docs/templates/migration-spec.md +4 -4
  189. package/tooling/scripts/context_checkpoint.py +5 -15
  190. package/tooling/scripts/cost_dashboard.py +610 -13
  191. package/tooling/scripts/create-persona.py +1 -12
  192. package/tooling/scripts/create-persona.sh +44 -44
  193. package/tooling/scripts/lib/__init__.py +12 -1
  194. package/tooling/scripts/lib/agent_handoff.py +11 -2
  195. package/tooling/scripts/lib/agent_router.py +31 -10
  196. package/tooling/scripts/lib/colors.py +106 -0
  197. package/tooling/scripts/lib/context_monitor.py +766 -0
  198. package/tooling/scripts/lib/cost_config.py +229 -10
  199. package/tooling/scripts/lib/cost_display.py +20 -45
  200. package/tooling/scripts/lib/cost_tracker.py +462 -15
  201. package/tooling/scripts/lib/currency_converter.py +28 -5
  202. package/tooling/scripts/lib/pair_programming.py +102 -3
  203. package/tooling/scripts/lib/personality_system.py +949 -0
  204. package/tooling/scripts/lib/platform.py +55 -0
  205. package/tooling/scripts/lib/shared_memory.py +9 -3
  206. package/tooling/scripts/lib/swarm_orchestrator.py +514 -75
  207. package/tooling/scripts/lib/validation_loop.py +1014 -0
  208. package/tooling/scripts/memory_summarize.py +9 -2
  209. package/tooling/scripts/new-doc.py +2 -9
  210. package/tooling/scripts/personalize_agent.py +1 -12
  211. package/tooling/scripts/rollback-migration.sh +60 -60
  212. package/tooling/scripts/run-collab.ps1 +16 -16
  213. package/tooling/scripts/run-collab.py +88 -53
  214. package/tooling/scripts/run-collab.sh +4 -4
  215. package/tooling/scripts/run-story.py +278 -20
  216. package/tooling/scripts/run-story.sh +3 -3
  217. package/tooling/scripts/setup-checkpoint-service.py +2 -9
  218. package/tooling/scripts/tech-debt-tracker.py +1 -12
  219. package/tooling/scripts/test_adversarial_swarm.py +452 -0
  220. package/tooling/scripts/validate-overrides.py +1 -10
  221. package/tooling/scripts/validate-overrides.sh +40 -40
  222. package/tooling/scripts/validate_loop.py +162 -0
  223. package/tooling/scripts/validate_setup.py +2 -30
  224. package/.claude/skills/init/SKILL.md +0 -496
@@ -3,14 +3,19 @@
3
3
  Cost Dashboard - CLI for viewing and managing cost data.
4
4
 
5
5
  Provides commands for viewing session history, generating summaries,
6
- and exporting cost reports in multiple formats.
6
+ subscription usage tracking, model efficiency metrics, usage projections,
7
+ and exporting comprehensive analytics reports.
7
8
 
8
9
  Usage:
9
- python cost_dashboard.py # Show current/latest session
10
- python cost_dashboard.py --history 10 # Show last 10 sessions
11
- python cost_dashboard.py --summary # Show aggregate summary
12
- python cost_dashboard.py --story 3-5 # Show costs for story
13
- python cost_dashboard.py --export costs.csv # Export to file
10
+ python cost_dashboard.py # Show current/latest session
11
+ python cost_dashboard.py --history 10 # Show last 10 sessions
12
+ python cost_dashboard.py --summary # Show aggregate summary
13
+ python cost_dashboard.py --subscription # Show subscription usage %
14
+ python cost_dashboard.py --efficiency # Show model efficiency
15
+ python cost_dashboard.py --set-plan pro # Set subscription plan
16
+ python cost_dashboard.py --story 3-5 # Show costs for story
17
+ python cost_dashboard.py --export costs.csv # Export to file
18
+ python cost_dashboard.py --schedule-export r.md # Export analytics report
14
19
  """
15
20
 
16
21
  import argparse
@@ -27,6 +32,7 @@ from typing import Optional
27
32
  # Add lib directory for imports
28
33
  sys.path.insert(0, str(Path(__file__).parent / "lib"))
29
34
 
35
+ from cost_config import SUBSCRIPTION_PLANS, get_config
30
36
  from cost_display import Colors
31
37
  from cost_tracker import SESSIONS_DIR, CostTracker, SessionCost
32
38
  from currency_converter import get_converter
@@ -255,9 +261,258 @@ class CostDashboard:
255
261
 
256
262
  print("\n".join(lines))
257
263
 
264
+ def _format_subscription_bar(self, percentage: float, width: int = 30) -> str:
265
+ """Create a progress bar for subscription usage."""
266
+ filled = int((percentage / 100) * width)
267
+ filled = min(filled, width) # Cap at width even if over 100%
268
+
269
+ # Color based on percentage
270
+ if percentage >= 100:
271
+ color = Colors.RED
272
+ elif percentage >= 90:
273
+ color = Colors.RED
274
+ elif percentage >= 75:
275
+ color = Colors.YELLOW
276
+ else:
277
+ color = Colors.GREEN
278
+
279
+ bar = "█" * filled + "░" * (width - filled)
280
+ return f"{color}{bar}{Colors.RESET}"
281
+
282
+ def show_subscription(self):
283
+ """Display subscription usage status with projection."""
284
+ config = get_config()
285
+
286
+ if config.subscription_token_limit <= 0:
287
+ print(f"{Colors.YELLOW}Subscription tracking not configured.{Colors.RESET}")
288
+ print()
289
+ print("To enable, use one of the following methods:")
290
+ print()
291
+ print("1. Set a plan preset (recommended):")
292
+ print(" export SUBSCRIPTION_PLAN=pro")
293
+ print()
294
+ print(" Available plans:")
295
+ for name, info in SUBSCRIPTION_PLANS.items():
296
+ print(
297
+ f" {name:<12} {self._format_tokens(info['token_limit']):>8} tokens/month ({info['description']})"
298
+ )
299
+ print()
300
+ print("2. Set a custom token limit:")
301
+ print(" export SUBSCRIPTION_TOKEN_LIMIT=5000000")
302
+ print()
303
+ print("3. Use config file: tooling/.automation/costs/config.json")
304
+ print(' {"subscription_plan": "pro"}')
305
+ return
306
+
307
+ sub = CostTracker.get_subscription_percentage(
308
+ config.subscription_token_limit,
309
+ config.subscription_billing_period_days,
310
+ )
311
+ projection = CostTracker.get_usage_projection(
312
+ config.subscription_token_limit,
313
+ config.subscription_billing_period_days,
314
+ )
315
+
316
+ lines = []
317
+
318
+ # Header
319
+ lines.append(
320
+ f"{Colors.CYAN}{self._box_line(self.BOX_TOP_LEFT, self.BOX_TOP_RIGHT)}{Colors.RESET}"
321
+ )
322
+ title = "SUBSCRIPTION USAGE"
323
+ lines.append(
324
+ f"{Colors.CYAN}{self._content_line(Colors.BOLD + title + Colors.RESET, 'center')}{Colors.RESET}"
325
+ )
326
+ lines.append(
327
+ f"{Colors.CYAN}{self._box_line(self.BOX_T_LEFT, self.BOX_T_RIGHT)}{Colors.RESET}"
328
+ )
329
+
330
+ # Plan info
331
+ plan_info = config.get_subscription_plan_info()
332
+ lines.append(self._empty_line())
333
+ lines.append(
334
+ self._content_line(f"Plan: {Colors.BOLD_CYAN}{plan_info['description']}{Colors.RESET}")
335
+ )
336
+
337
+ # Status indicator
338
+ status_colors = {
339
+ "ok": Colors.GREEN,
340
+ "warning": Colors.YELLOW,
341
+ "critical": Colors.RED,
342
+ "exceeded": Colors.RED,
343
+ }
344
+ status_color = status_colors.get(sub["status"], Colors.WHITE)
345
+ status_text = sub["status"].upper()
346
+
347
+ lines.append(
348
+ self._content_line(
349
+ f"Status: {status_color}{Colors.BOLD}{status_text}{Colors.RESET} "
350
+ f"Billing Period: {Colors.BOLD}{sub['billing_period_days']} days{Colors.RESET}"
351
+ )
352
+ )
353
+
354
+ # Progress bar
355
+ lines.append(self._empty_line())
356
+ lines.append(self._section_header("USAGE"))
357
+ bar = self._format_subscription_bar(sub["percentage"])
358
+ lines.append(
359
+ self._content_line(f"{bar} {status_color}{sub['percentage']:.1f}%{Colors.RESET}")
360
+ )
361
+
362
+ # Token details
363
+ lines.append(self._empty_line())
364
+ lines.append(
365
+ self._content_line(
366
+ f"Used: {Colors.BOLD}{self._format_tokens(sub['used_tokens']):>10}{Colors.RESET} tokens"
367
+ )
368
+ )
369
+ lines.append(
370
+ self._content_line(
371
+ f"Limit: {Colors.BOLD}{self._format_tokens(sub['limit_tokens']):>10}{Colors.RESET} tokens"
372
+ )
373
+ )
374
+
375
+ remaining_color = Colors.GREEN if sub["remaining_tokens"] > 0 else Colors.RED
376
+ remaining_sign = "" if sub["remaining_tokens"] >= 0 else "-"
377
+ lines.append(
378
+ self._content_line(
379
+ f"Remaining: {remaining_color}{remaining_sign}{self._format_tokens(abs(sub['remaining_tokens'])):>10}{Colors.RESET} tokens"
380
+ )
381
+ )
382
+
383
+ # Projection / Forecast
384
+ lines.append(self._empty_line())
385
+ lines.append(self._section_header("PROJECTION"))
386
+
387
+ proj_color = Colors.GREEN if projection["on_track"] else Colors.YELLOW
388
+ if projection["days_until_limit"] is not None and projection["days_until_limit"] <= 0:
389
+ proj_color = Colors.RED
390
+
391
+ lines.append(
392
+ self._content_line(
393
+ f"Daily Avg: {Colors.BOLD}{self._format_tokens(projection['daily_average']):>10}{Colors.RESET} tokens/day"
394
+ )
395
+ )
396
+ lines.append(
397
+ self._content_line(
398
+ f"Projected: {Colors.BOLD}{self._format_tokens(projection['projected_end_usage']):>10}{Colors.RESET} tokens by period end"
399
+ )
400
+ )
401
+
402
+ if projection["days_until_limit"] is not None:
403
+ days_str = (
404
+ f"{projection['days_until_limit']:.0f}"
405
+ if projection["days_until_limit"] > 0
406
+ else "0"
407
+ )
408
+ lines.append(
409
+ self._content_line(
410
+ f"Days to Limit: {proj_color}{Colors.BOLD}{days_str:>10}{Colors.RESET} days"
411
+ )
412
+ )
413
+
414
+ lines.append(self._empty_line())
415
+ lines.append(self._content_line(f"{proj_color}{projection['message']}{Colors.RESET}"))
416
+
417
+ # Cost in period
418
+ lines.append(self._empty_line())
419
+ lines.append(self._section_header("PERIOD COST"))
420
+ lines.append(
421
+ self._content_line(
422
+ f"Total Cost: {Colors.BOLD_GREEN}${sub['total_cost_usd']:.2f}{Colors.RESET} "
423
+ f"Sessions: {Colors.BOLD}{sub['total_sessions']}{Colors.RESET}"
424
+ )
425
+ )
426
+
427
+ # Multi-currency
428
+ lines.append(self._empty_line())
429
+ lines.append(self._content_line(self.converter.format_all(sub["total_cost_usd"], " | ")))
430
+
431
+ # Footer
432
+ lines.append(self._empty_line())
433
+ lines.append(
434
+ f"{Colors.CYAN}{self._box_line(self.BOX_BOTTOM_LEFT, self.BOX_BOTTOM_RIGHT)}{Colors.RESET}"
435
+ )
436
+
437
+ print("\n".join(lines))
438
+
439
+ def show_efficiency(self):
440
+ """Display model efficiency metrics."""
441
+ efficiency = CostTracker.get_model_efficiency()
442
+
443
+ if not efficiency:
444
+ print(f"{Colors.YELLOW}No usage data available for efficiency analysis.{Colors.RESET}")
445
+ return
446
+
447
+ lines = []
448
+
449
+ # Header
450
+ lines.append(
451
+ f"{Colors.CYAN}{self._box_line(self.BOX_TOP_LEFT, self.BOX_TOP_RIGHT)}{Colors.RESET}"
452
+ )
453
+ title = "MODEL EFFICIENCY"
454
+ lines.append(
455
+ f"{Colors.CYAN}{self._content_line(Colors.BOLD + title + Colors.RESET, 'center')}{Colors.RESET}"
456
+ )
457
+ lines.append(
458
+ f"{Colors.CYAN}{self._box_line(self.BOX_T_LEFT, self.BOX_T_RIGHT)}{Colors.RESET}"
459
+ )
460
+
461
+ # Table header
462
+ lines.append(self._empty_line())
463
+ lines.append(
464
+ self._content_line(
465
+ f"{Colors.DIM}{'Model':<12} {'$/1K Out':>10} {'Out/In':>8} {'Calls':>8} {'Cost':>10}{Colors.RESET}"
466
+ )
467
+ )
468
+ lines.append(self._content_line(f"{Colors.DIM}{self.BOX_LINE * 52}{Colors.RESET}"))
469
+
470
+ # Models (sorted by efficiency - lowest cost per output first)
471
+ best_model = list(efficiency.keys())[0] if efficiency else None
472
+ for model, stats in efficiency.items():
473
+ # Highlight the most efficient model
474
+ if model == best_model:
475
+ model_display = f"{Colors.GREEN}{model:<12}{Colors.RESET}"
476
+ efficiency_badge = f" {Colors.GREEN}[BEST]{Colors.RESET}"
477
+ else:
478
+ model_display = f"{model:<12}"
479
+ efficiency_badge = ""
480
+
481
+ lines.append(
482
+ self._content_line(
483
+ f"{model_display} ${stats['cost_per_1k_output']:>9.4f} "
484
+ f"{stats['output_input_ratio']:>7.2f}x "
485
+ f"{stats['total_calls']:>8} "
486
+ f"${stats['total_cost']:>9.2f}{efficiency_badge}"
487
+ )
488
+ )
489
+
490
+ # Summary
491
+ lines.append(self._empty_line())
492
+ lines.append(self._section_header("METRICS EXPLAINED"))
493
+ lines.append(
494
+ self._content_line(
495
+ f"{Colors.DIM}$/1K Out = Cost per 1,000 output tokens (lower is better){Colors.RESET}"
496
+ )
497
+ )
498
+ lines.append(
499
+ self._content_line(
500
+ f"{Colors.DIM}Out/In = Output tokens per input token (efficiency ratio){Colors.RESET}"
501
+ )
502
+ )
503
+
504
+ # Footer
505
+ lines.append(self._empty_line())
506
+ lines.append(
507
+ f"{Colors.CYAN}{self._box_line(self.BOX_BOTTOM_LEFT, self.BOX_BOTTOM_RIGHT)}{Colors.RESET}"
508
+ )
509
+
510
+ print("\n".join(lines))
511
+
258
512
  def show_summary(self, days: int = 30):
259
513
  """Display aggregate summary."""
260
514
  stats = CostTracker.get_aggregate_stats(days)
515
+ config = get_config()
261
516
 
262
517
  lines = []
263
518
 
@@ -273,6 +528,36 @@ class CostDashboard:
273
528
  f"{Colors.CYAN}{self._box_line(self.BOX_T_LEFT, self.BOX_T_RIGHT)}{Colors.RESET}"
274
529
  )
275
530
 
531
+ # Subscription status (if configured)
532
+ if config.subscription_token_limit > 0:
533
+ sub = CostTracker.get_subscription_percentage(
534
+ config.subscription_token_limit,
535
+ config.subscription_billing_period_days,
536
+ )
537
+ status_colors = {
538
+ "ok": Colors.GREEN,
539
+ "warning": Colors.YELLOW,
540
+ "critical": Colors.RED,
541
+ "exceeded": Colors.RED,
542
+ }
543
+ status_color = status_colors.get(sub["status"], Colors.WHITE)
544
+
545
+ lines.append(self._empty_line())
546
+ lines.append(self._section_header("SUBSCRIPTION"))
547
+ bar = self._format_subscription_bar(sub["percentage"], width=25)
548
+ lines.append(
549
+ self._content_line(
550
+ f"{bar} {status_color}{sub['percentage']:.1f}%{Colors.RESET} of "
551
+ f"{self._format_tokens(sub['limit_tokens'])} token limit"
552
+ )
553
+ )
554
+ lines.append(
555
+ self._content_line(
556
+ f"Used: {self._format_tokens(sub['used_tokens'])} | "
557
+ f"Remaining: {self._format_tokens(max(0, sub['remaining_tokens']))}"
558
+ )
559
+ )
560
+
276
561
  # Overview
277
562
  lines.append(self._empty_line())
278
563
  lines.append(self._section_header("OVERVIEW"))
@@ -501,6 +786,264 @@ class CostDashboard:
501
786
  with open(path, "w") as f:
502
787
  json.dump(data, f, indent=2)
503
788
 
789
+ def export_analytics_report(self, filepath: str, days: int = 30):
790
+ """
791
+ Export comprehensive analytics report with trends, rankings, and comparisons.
792
+
793
+ Includes:
794
+ - Daily/Weekly usage trends
795
+ - Per-story cost rankings
796
+ - Period comparison (current vs previous)
797
+ - API rate statistics
798
+ - Model efficiency metrics
799
+ """
800
+ path = Path(filepath)
801
+ ext = path.suffix.lower()
802
+
803
+ # Gather all analytics data
804
+ config = get_config()
805
+ daily_usage = CostTracker.get_daily_usage(days)
806
+ story_rankings = CostTracker.get_story_rankings(days, limit=15)
807
+ comparison = CostTracker.get_period_comparison(days)
808
+ api_rates = CostTracker.get_api_rate_stats(days)
809
+ efficiency = CostTracker.get_model_efficiency()
810
+ stats = CostTracker.get_aggregate_stats(days)
811
+
812
+ # Subscription data if configured
813
+ subscription = None
814
+ projection = None
815
+ if config.subscription_token_limit > 0:
816
+ subscription = CostTracker.get_subscription_percentage(
817
+ config.subscription_token_limit,
818
+ config.subscription_billing_period_days,
819
+ )
820
+ projection = CostTracker.get_usage_projection(
821
+ config.subscription_token_limit,
822
+ config.subscription_billing_period_days,
823
+ )
824
+
825
+ if ext == ".json":
826
+ self._export_analytics_json(
827
+ path,
828
+ {
829
+ "generated_at": datetime.now().isoformat(),
830
+ "period_days": days,
831
+ "summary": stats,
832
+ "subscription": subscription,
833
+ "projection": projection,
834
+ "daily_usage": daily_usage,
835
+ "story_rankings": story_rankings,
836
+ "period_comparison": comparison,
837
+ "api_rates": api_rates,
838
+ "model_efficiency": efficiency,
839
+ },
840
+ )
841
+ elif ext == ".md":
842
+ self._export_analytics_markdown(
843
+ path,
844
+ days,
845
+ stats,
846
+ subscription,
847
+ projection,
848
+ daily_usage,
849
+ story_rankings,
850
+ comparison,
851
+ api_rates,
852
+ efficiency,
853
+ )
854
+ else:
855
+ print(f"{Colors.RED}Unsupported format: {ext}{Colors.RESET}")
856
+ print("Supported formats for analytics: .json, .md")
857
+ return
858
+
859
+ print(f"{Colors.GREEN}Analytics report exported to: {filepath}{Colors.RESET}")
860
+ print(f" Period: {days} days")
861
+ print(f" Total Sessions: {stats['total_sessions']}")
862
+ print(f" Total Cost: ${stats['total_cost_usd']:.2f}")
863
+
864
+ def _export_analytics_json(self, path: Path, data: dict):
865
+ """Export analytics to JSON."""
866
+ with open(path, "w") as f:
867
+ json.dump(data, f, indent=2, default=str)
868
+
869
+ def _export_analytics_markdown(
870
+ self,
871
+ path: Path,
872
+ days: int,
873
+ stats: dict,
874
+ subscription: dict,
875
+ projection: dict,
876
+ daily_usage: list,
877
+ story_rankings: list,
878
+ comparison: dict,
879
+ api_rates: dict,
880
+ efficiency: dict,
881
+ ):
882
+ """Export comprehensive analytics to Markdown."""
883
+ lines = [
884
+ "# Cost Analytics Report",
885
+ "",
886
+ f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}",
887
+ f"Period: Last {days} days",
888
+ "",
889
+ ]
890
+
891
+ # Summary
892
+ lines.extend(
893
+ [
894
+ "## Summary",
895
+ "",
896
+ "| Metric | Value |",
897
+ "|--------|-------|",
898
+ f"| Total Sessions | {stats['total_sessions']} |",
899
+ f"| Total Tokens | {stats['total_tokens']:,} |",
900
+ f"| Total Cost | ${stats['total_cost_usd']:.2f} |",
901
+ f"| Avg per Session | ${stats.get('average_per_session', 0):.2f} |",
902
+ "",
903
+ ]
904
+ )
905
+
906
+ # Subscription Status
907
+ if subscription:
908
+ lines.extend(
909
+ [
910
+ "## Subscription Status",
911
+ "",
912
+ "| Metric | Value |",
913
+ "|--------|-------|",
914
+ f"| Usage | {subscription['percentage']:.1f}% |",
915
+ f"| Used Tokens | {subscription['used_tokens']:,} |",
916
+ f"| Limit | {subscription['limit_tokens']:,} |",
917
+ f"| Remaining | {subscription['remaining_tokens']:,} |",
918
+ f"| Status | {subscription['status'].upper()} |",
919
+ "",
920
+ ]
921
+ )
922
+
923
+ if projection:
924
+ lines.extend(
925
+ [
926
+ "### Projection",
927
+ "",
928
+ f"- **Daily Average**: {projection['daily_average']:,} tokens/day",
929
+ f"- **Projected End Usage**: {projection['projected_end_usage']:,} tokens",
930
+ f"- **Days Until Limit**: {projection['days_until_limit'] or 'N/A'}",
931
+ f"- **Forecast**: {projection['message']}",
932
+ "",
933
+ ]
934
+ )
935
+
936
+ # Period Comparison
937
+ lines.extend(
938
+ [
939
+ "## Period Comparison",
940
+ "",
941
+ f"Comparing current {days} days vs previous {days} days:",
942
+ "",
943
+ "| Metric | Current | Previous | Change |",
944
+ "|--------|---------|----------|--------|",
945
+ ]
946
+ )
947
+
948
+ curr = comparison["current_period"]
949
+ prev = comparison["previous_period"]
950
+ delta = comparison["delta"]
951
+
952
+ token_arrow = "+" if delta["tokens"] >= 0 else ""
953
+ cost_arrow = "+" if delta["cost_usd"] >= 0 else ""
954
+
955
+ lines.extend(
956
+ [
957
+ f"| Tokens | {curr['tokens']:,} | {prev['tokens']:,} | {token_arrow}{delta['tokens_pct']:.1f}% |",
958
+ f"| Cost | ${curr['cost_usd']:.2f} | ${prev['cost_usd']:.2f} | {cost_arrow}{delta['cost_pct']:.1f}% |",
959
+ f"| Sessions | {curr['sessions']} | {prev['sessions']} | - |",
960
+ "",
961
+ ]
962
+ )
963
+
964
+ # Daily Trends
965
+ if daily_usage:
966
+ lines.extend(
967
+ [
968
+ "## Daily Usage Trends",
969
+ "",
970
+ "| Date | Tokens | Cost | Sessions |",
971
+ "|------|--------|------|----------|",
972
+ ]
973
+ )
974
+ for day in daily_usage[-14:]: # Last 14 days
975
+ lines.append(
976
+ f"| {day['date']} | {day['tokens']:,} | ${day['cost_usd']:.2f} | {day['sessions']} |"
977
+ )
978
+ lines.append("")
979
+
980
+ # Story Rankings
981
+ if story_rankings:
982
+ lines.extend(
983
+ [
984
+ "## Top Stories by Token Usage",
985
+ "",
986
+ "| Rank | Story | Tokens | Cost | Sessions |",
987
+ "|------|-------|--------|------|----------|",
988
+ ]
989
+ )
990
+ for i, story in enumerate(story_rankings[:10], 1):
991
+ lines.append(
992
+ f"| {i} | {story['story_key']} | {story['total_tokens']:,} | "
993
+ f"${story['total_cost_usd']:.2f} | {story['sessions']} |"
994
+ )
995
+ lines.append("")
996
+
997
+ # API Rate Statistics
998
+ lines.extend(
999
+ [
1000
+ "## API Rate Statistics",
1001
+ "",
1002
+ "| Metric | Value |",
1003
+ "|--------|-------|",
1004
+ f"| Total Calls | {api_rates['total_calls']} |",
1005
+ f"| Calls/Day (avg) | {api_rates['calls_per_day']} |",
1006
+ f"| Calls/Hour (avg) | {api_rates['calls_per_hour']} |",
1007
+ f"| Peak Hour | {api_rates['peak_hour']}:00 ({api_rates['peak_hour_calls']} calls) |"
1008
+ if api_rates["peak_hour"] is not None
1009
+ else "| Peak Hour | N/A |",
1010
+ f"| Peak Day | {api_rates['peak_day']} ({api_rates['peak_day_calls']} calls) |"
1011
+ if api_rates["peak_day"]
1012
+ else "| Peak Day | N/A |",
1013
+ "",
1014
+ ]
1015
+ )
1016
+
1017
+ # Model Efficiency
1018
+ if efficiency:
1019
+ lines.extend(
1020
+ [
1021
+ "## Model Efficiency",
1022
+ "",
1023
+ "| Model | $/1K Output | Out/In Ratio | Calls | Total Cost |",
1024
+ "|-------|-------------|--------------|-------|------------|",
1025
+ ]
1026
+ )
1027
+ for model, stats in efficiency.items():
1028
+ lines.append(
1029
+ f"| {model} | ${stats['cost_per_1k_output']:.4f} | "
1030
+ f"{stats['output_input_ratio']:.2f}x | {stats['total_calls']} | "
1031
+ f"${stats['total_cost']:.2f} |"
1032
+ )
1033
+ lines.append("")
1034
+
1035
+ # Footer
1036
+ lines.extend(
1037
+ [
1038
+ "---",
1039
+ "",
1040
+ "Report generated by Devflow Cost Dashboard",
1041
+ ]
1042
+ )
1043
+
1044
+ with open(path, "w") as f:
1045
+ f.write("\n".join(lines))
1046
+
504
1047
  def _export_markdown(self, path: Path, sessions: list[SessionCost]):
505
1048
  """Export to Markdown."""
506
1049
  total_cost = sum(s.total_cost_usd for s in sessions)
@@ -548,17 +1091,32 @@ def main():
548
1091
  formatter_class=argparse.RawDescriptionHelpFormatter,
549
1092
  epilog="""
550
1093
  Examples:
551
- python cost_dashboard.py # Show latest session
552
- python cost_dashboard.py --history 10 # Show last 10 sessions
553
- python cost_dashboard.py --summary # Show 30-day summary
554
- python cost_dashboard.py --story 3-5 # Show costs for story
555
- python cost_dashboard.py --export costs.csv # Export to CSV
556
- python cost_dashboard.py --export report.md # Export to Markdown
1094
+ python cost_dashboard.py # Show latest session
1095
+ python cost_dashboard.py --history 10 # Show last 10 sessions
1096
+ python cost_dashboard.py --summary # Show 30-day summary
1097
+ python cost_dashboard.py --subscription # Show subscription usage %
1098
+ python cost_dashboard.py --efficiency # Show model efficiency
1099
+ python cost_dashboard.py --set-plan pro # Set subscription to pro plan
1100
+ python cost_dashboard.py --story 3-5 # Show costs for story
1101
+ python cost_dashboard.py --export costs.csv # Export to CSV
1102
+ python cost_dashboard.py --schedule-export r.md # Export analytics report
557
1103
  """,
558
1104
  )
559
1105
 
560
1106
  parser.add_argument("--history", "-H", type=int, metavar="N", help="Show last N sessions")
561
1107
  parser.add_argument("--summary", "-s", action="store_true", help="Show aggregate summary")
1108
+ parser.add_argument(
1109
+ "--subscription", "-S", action="store_true", help="Show subscription usage percentage"
1110
+ )
1111
+ parser.add_argument(
1112
+ "--efficiency", "-E", action="store_true", help="Show model efficiency metrics"
1113
+ )
1114
+ parser.add_argument(
1115
+ "--set-plan",
1116
+ type=str,
1117
+ metavar="PLAN",
1118
+ help="Set subscription plan (free, developer, pro, scale, enterprise)",
1119
+ )
562
1120
  parser.add_argument(
563
1121
  "--days", "-d", type=int, default=30, help="Number of days for summary (default: 30)"
564
1122
  )
@@ -566,6 +1124,12 @@ Examples:
566
1124
  parser.add_argument(
567
1125
  "--export", "-e", type=str, metavar="FILE", help="Export to file (.csv, .json, .md)"
568
1126
  )
1127
+ parser.add_argument(
1128
+ "--schedule-export",
1129
+ type=str,
1130
+ metavar="FILE",
1131
+ help="Export comprehensive report with analytics to file",
1132
+ )
569
1133
  parser.add_argument("--from-date", type=str, metavar="YYYY-MM-DD", help="Filter from date")
570
1134
  parser.add_argument("--to-date", type=str, metavar="YYYY-MM-DD", help="Filter to date")
571
1135
 
@@ -593,8 +1157,41 @@ Examples:
593
1157
  return 1
594
1158
 
595
1159
  # Execute command
596
- if args.export:
1160
+ if args.set_plan:
1161
+ # Set subscription plan
1162
+ config = get_config()
1163
+ if config.set_subscription_plan(args.set_plan):
1164
+ config_file = (
1165
+ Path(__file__).parent
1166
+ / "lib"
1167
+ / ".."
1168
+ / ".."
1169
+ / ".automation"
1170
+ / "costs"
1171
+ / "config.json"
1172
+ )
1173
+ config_file = config_file.resolve()
1174
+ config.save(config_file)
1175
+ plan_info = config.get_subscription_plan_info()
1176
+ print(
1177
+ f"{Colors.GREEN}Subscription plan set to: {plan_info['description']}{Colors.RESET}"
1178
+ )
1179
+ print(
1180
+ f" Token Limit: {dashboard._format_tokens(plan_info['token_limit'])} tokens/month"
1181
+ )
1182
+ print(f" Config saved to: {config_file}")
1183
+ else:
1184
+ print(f"{Colors.RED}Unknown plan: {args.set_plan}{Colors.RESET}")
1185
+ print(f"Available plans: {', '.join(SUBSCRIPTION_PLANS.keys())}")
1186
+ return 1
1187
+ elif args.schedule_export:
1188
+ dashboard.export_analytics_report(args.schedule_export, args.days)
1189
+ elif args.export:
597
1190
  dashboard.export_data(args.export, sessions)
1191
+ elif args.efficiency:
1192
+ dashboard.show_efficiency()
1193
+ elif args.subscription:
1194
+ dashboard.show_subscription()
598
1195
  elif args.summary:
599
1196
  dashboard.show_summary(args.days)
600
1197
  elif args.story: