@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
@@ -24,6 +24,36 @@ DEFAULT_WARNING_PERCENT = 75
24
24
  DEFAULT_CRITICAL_PERCENT = 90
25
25
  DEFAULT_AUTO_STOP = True
26
26
 
27
+ # Subscription defaults (monthly token limits)
28
+ # Set to 0 to disable subscription tracking
29
+ DEFAULT_SUBSCRIPTION_TOKEN_LIMIT = 0 # No default limit - user must configure
30
+ DEFAULT_SUBSCRIPTION_BILLING_PERIOD_DAYS = 30
31
+
32
+ # Common subscription plan presets (tokens per month)
33
+ # Based on typical Anthropic API plans as of December 2025
34
+ SUBSCRIPTION_PLANS = {
35
+ "free": {
36
+ "token_limit": 100_000, # 100K tokens/month
37
+ "description": "Free tier / Trial",
38
+ },
39
+ "developer": {
40
+ "token_limit": 1_000_000, # 1M tokens/month
41
+ "description": "Developer plan",
42
+ },
43
+ "pro": {
44
+ "token_limit": 5_000_000, # 5M tokens/month
45
+ "description": "Pro / Team plan",
46
+ },
47
+ "scale": {
48
+ "token_limit": 20_000_000, # 20M tokens/month
49
+ "description": "Scale plan",
50
+ },
51
+ "enterprise": {
52
+ "token_limit": 100_000_000, # 100M tokens/month
53
+ "description": "Enterprise plan",
54
+ },
55
+ }
56
+
27
57
  DEFAULT_CURRENCY_RATES = {
28
58
  "USD": 1.0,
29
59
  "EUR": 0.92,
@@ -51,6 +81,13 @@ class CostConfig:
51
81
  critical_percent: int = DEFAULT_CRITICAL_PERCENT
52
82
  auto_stop: bool = DEFAULT_AUTO_STOP
53
83
 
84
+ # Subscription settings (for tracking usage against API plan limits)
85
+ subscription_token_limit: int = DEFAULT_SUBSCRIPTION_TOKEN_LIMIT
86
+ subscription_billing_period_days: int = DEFAULT_SUBSCRIPTION_BILLING_PERIOD_DAYS
87
+ subscription_plan: str = (
88
+ "" # Plan name if using preset (free, developer, pro, scale, enterprise)
89
+ )
90
+
54
91
  # Currency settings
55
92
  display_currency: str = "USD"
56
93
  currency_rates: dict[str, float] = field(default_factory=lambda: DEFAULT_CURRENCY_RATES.copy())
@@ -61,33 +98,84 @@ class CostConfig:
61
98
  """Load configuration from environment variables."""
62
99
  config = cls()
63
100
 
101
+ def _safe_float(env_var: str, default: float) -> float:
102
+ """Safely convert env var to float, returning default on failure."""
103
+ value = os.getenv(env_var)
104
+ if not value:
105
+ return default
106
+ try:
107
+ return float(value)
108
+ except ValueError:
109
+ print(f"Warning: Invalid float value for {env_var}: '{value}', using default")
110
+ return default
111
+
112
+ def _safe_int(env_var: str, default: int) -> int:
113
+ """Safely convert env var to int, returning default on failure."""
114
+ value = os.getenv(env_var)
115
+ if not value:
116
+ return default
117
+ try:
118
+ return int(value)
119
+ except ValueError:
120
+ print(f"Warning: Invalid int value for {env_var}: '{value}', using default")
121
+ return default
122
+
64
123
  # Budget limits
65
124
  if os.getenv("MAX_BUDGET_CONTEXT"):
66
- config.budget_context = float(os.getenv("MAX_BUDGET_CONTEXT"))
125
+ config.budget_context = _safe_float("MAX_BUDGET_CONTEXT", config.budget_context)
67
126
  if os.getenv("MAX_BUDGET_DEV"):
68
- config.budget_dev = float(os.getenv("MAX_BUDGET_DEV"))
127
+ config.budget_dev = _safe_float("MAX_BUDGET_DEV", config.budget_dev)
69
128
  if os.getenv("MAX_BUDGET_REVIEW"):
70
- config.budget_review = float(os.getenv("MAX_BUDGET_REVIEW"))
129
+ config.budget_review = _safe_float("MAX_BUDGET_REVIEW", config.budget_review)
71
130
 
72
131
  # Alert thresholds
73
132
  if os.getenv("COST_WARNING_PERCENT"):
74
- config.warning_percent = int(os.getenv("COST_WARNING_PERCENT"))
133
+ config.warning_percent = _safe_int("COST_WARNING_PERCENT", config.warning_percent)
75
134
  if os.getenv("COST_CRITICAL_PERCENT"):
76
- config.critical_percent = int(os.getenv("COST_CRITICAL_PERCENT"))
135
+ config.critical_percent = _safe_int("COST_CRITICAL_PERCENT", config.critical_percent)
77
136
  if os.getenv("COST_AUTO_STOP"):
78
137
  config.auto_stop = os.getenv("COST_AUTO_STOP").lower() in ("true", "1", "yes")
79
138
 
139
+ # Subscription settings
140
+ # Check for plan preset first (e.g., SUBSCRIPTION_PLAN=pro)
141
+ if os.getenv("SUBSCRIPTION_PLAN"):
142
+ plan_name = os.getenv("SUBSCRIPTION_PLAN").lower()
143
+ if plan_name in SUBSCRIPTION_PLANS:
144
+ config.subscription_plan = plan_name
145
+ config.subscription_token_limit = SUBSCRIPTION_PLANS[plan_name]["token_limit"]
146
+ else:
147
+ print(
148
+ f"Warning: Unknown subscription plan '{plan_name}'. "
149
+ f"Valid plans: {', '.join(SUBSCRIPTION_PLANS.keys())}"
150
+ )
151
+
152
+ # Direct token limit overrides plan preset
153
+ if os.getenv("SUBSCRIPTION_TOKEN_LIMIT"):
154
+ config.subscription_token_limit = _safe_int(
155
+ "SUBSCRIPTION_TOKEN_LIMIT", config.subscription_token_limit
156
+ )
157
+ if os.getenv("SUBSCRIPTION_BILLING_PERIOD_DAYS"):
158
+ config.subscription_billing_period_days = _safe_int(
159
+ "SUBSCRIPTION_BILLING_PERIOD_DAYS", config.subscription_billing_period_days
160
+ )
161
+
80
162
  # Currency settings
81
163
  if os.getenv("COST_DISPLAY_CURRENCY"):
82
164
  config.display_currency = os.getenv("COST_DISPLAY_CURRENCY")
83
165
 
84
166
  # Currency rates from environment
85
167
  if os.getenv("CURRENCY_RATE_EUR"):
86
- config.currency_rates["EUR"] = float(os.getenv("CURRENCY_RATE_EUR"))
168
+ config.currency_rates["EUR"] = _safe_float(
169
+ "CURRENCY_RATE_EUR", config.currency_rates["EUR"]
170
+ )
87
171
  if os.getenv("CURRENCY_RATE_GBP"):
88
- config.currency_rates["GBP"] = float(os.getenv("CURRENCY_RATE_GBP"))
172
+ config.currency_rates["GBP"] = _safe_float(
173
+ "CURRENCY_RATE_GBP", config.currency_rates["GBP"]
174
+ )
89
175
  if os.getenv("CURRENCY_RATE_BRL"):
90
- config.currency_rates["BRL"] = float(os.getenv("CURRENCY_RATE_BRL"))
176
+ config.currency_rates["BRL"] = _safe_float(
177
+ "CURRENCY_RATE_BRL", config.currency_rates["BRL"]
178
+ )
91
179
 
92
180
  return config
93
181
 
@@ -119,6 +207,21 @@ class CostConfig:
119
207
  if "auto_stop" in data:
120
208
  config.auto_stop = bool(data["auto_stop"])
121
209
 
210
+ # Subscription settings
211
+ # Check for plan preset first
212
+ if "subscription_plan" in data:
213
+ plan_name = data["subscription_plan"].lower()
214
+ if plan_name in SUBSCRIPTION_PLANS:
215
+ config.subscription_plan = plan_name
216
+ config.subscription_token_limit = SUBSCRIPTION_PLANS[plan_name]["token_limit"]
217
+ # Direct token limit overrides plan preset
218
+ if "subscription_token_limit" in data:
219
+ config.subscription_token_limit = int(data["subscription_token_limit"])
220
+ if "subscription_billing_period_days" in data:
221
+ config.subscription_billing_period_days = int(
222
+ data["subscription_billing_period_days"]
223
+ )
224
+
122
225
  # Currency settings
123
226
  if "display_currency" in data:
124
227
  config.display_currency = data["display_currency"]
@@ -127,8 +230,10 @@ class CostConfig:
127
230
  if "display_currencies" in data:
128
231
  config.display_currencies = data["display_currencies"]
129
232
 
130
- except Exception as e:
131
- print(f"Warning: Could not load config file: {e}")
233
+ except json.JSONDecodeError as e:
234
+ print(f"Warning: Invalid JSON in config file: {e}")
235
+ except OSError as e:
236
+ print(f"Warning: Could not read config file: {e}")
132
237
 
133
238
  return config
134
239
 
@@ -141,6 +246,9 @@ class CostConfig:
141
246
  "warning_percent": self.warning_percent,
142
247
  "critical_percent": self.critical_percent,
143
248
  "auto_stop": self.auto_stop,
249
+ "subscription_plan": self.subscription_plan,
250
+ "subscription_token_limit": self.subscription_token_limit,
251
+ "subscription_billing_period_days": self.subscription_billing_period_days,
144
252
  "display_currency": self.display_currency,
145
253
  "currency_rates": self.currency_rates,
146
254
  "display_currencies": self.display_currencies,
@@ -170,6 +278,110 @@ class CostConfig:
170
278
  "stop": 1.0,
171
279
  }
172
280
 
281
+ def set_subscription_plan(self, plan_name: str) -> bool:
282
+ """
283
+ Set subscription based on a plan preset.
284
+
285
+ Args:
286
+ plan_name: One of: free, developer, pro, scale, enterprise
287
+
288
+ Returns:
289
+ True if plan was set successfully, False otherwise.
290
+ """
291
+ plan_name = plan_name.lower()
292
+ if plan_name not in SUBSCRIPTION_PLANS:
293
+ return False
294
+
295
+ self.subscription_plan = plan_name
296
+ self.subscription_token_limit = SUBSCRIPTION_PLANS[plan_name]["token_limit"]
297
+ return True
298
+
299
+ def get_subscription_plan_info(self) -> dict:
300
+ """Get information about the current subscription plan."""
301
+ if self.subscription_plan and self.subscription_plan in SUBSCRIPTION_PLANS:
302
+ plan = SUBSCRIPTION_PLANS[self.subscription_plan]
303
+ return {
304
+ "plan": self.subscription_plan,
305
+ "description": plan["description"],
306
+ "token_limit": self.subscription_token_limit,
307
+ "billing_period_days": self.subscription_billing_period_days,
308
+ }
309
+ elif self.subscription_token_limit > 0:
310
+ return {
311
+ "plan": "custom",
312
+ "description": "Custom token limit",
313
+ "token_limit": self.subscription_token_limit,
314
+ "billing_period_days": self.subscription_billing_period_days,
315
+ }
316
+ return {
317
+ "plan": "none",
318
+ "description": "Not configured",
319
+ "token_limit": 0,
320
+ "billing_period_days": self.subscription_billing_period_days,
321
+ }
322
+
323
+ @staticmethod
324
+ def get_available_plans() -> dict:
325
+ """Get all available subscription plan presets."""
326
+ return SUBSCRIPTION_PLANS.copy()
327
+
328
+ def auto_detect_plan(self, model: str = "sonnet") -> str:
329
+ """
330
+ Auto-detect subscription plan based on model usage.
331
+
332
+ Args:
333
+ model: The model being used (opus, sonnet, haiku)
334
+
335
+ Returns:
336
+ Detected plan name (free, developer, pro, scale, enterprise)
337
+ """
338
+ model_lower = model.lower()
339
+
340
+ # If already configured, return existing plan
341
+ if self.subscription_plan:
342
+ return self.subscription_plan
343
+
344
+ # Infer plan from model
345
+ if "opus" in model_lower:
346
+ # Opus users are typically on pro or higher
347
+ detected = "pro"
348
+ elif "sonnet" in model_lower:
349
+ # Sonnet could be developer or higher
350
+ detected = "developer"
351
+ elif "haiku" in model_lower:
352
+ # Haiku might be free tier
353
+ detected = "free"
354
+ else:
355
+ detected = "developer"
356
+
357
+ # Set and return the detected plan
358
+ self.set_subscription_plan(detected)
359
+ return detected
360
+
361
+ def ensure_plan_configured(
362
+ self, model: str = "sonnet", config_path: Optional[Path] = None
363
+ ) -> str:
364
+ """
365
+ Ensure a subscription plan is configured, auto-detecting if needed.
366
+
367
+ Args:
368
+ model: The model being used for auto-detection
369
+ config_path: Optional path to save config
370
+
371
+ Returns:
372
+ The configured or detected plan name
373
+ """
374
+ if self.subscription_plan and self.subscription_token_limit > 0:
375
+ return self.subscription_plan
376
+
377
+ plan = self.auto_detect_plan(model)
378
+
379
+ # Save to config file if path provided
380
+ if config_path:
381
+ self.save(config_path)
382
+
383
+ return plan
384
+
173
385
 
174
386
  # Global configuration instance
175
387
  _config: Optional[CostConfig] = None
@@ -214,6 +426,13 @@ if __name__ == "__main__":
214
426
  print(f"Critical at: {config.critical_percent}%")
215
427
  print(f"Auto-stop: {config.auto_stop}")
216
428
  print()
429
+ print("Subscription Settings:")
430
+ if config.subscription_token_limit > 0:
431
+ print(f" Token Limit: {config.subscription_token_limit:,} tokens")
432
+ print(f" Billing Period: {config.subscription_billing_period_days} days")
433
+ else:
434
+ print(" Not configured (set SUBSCRIPTION_TOKEN_LIMIT to enable)")
435
+ print()
217
436
  print(f"Display Currency: {config.display_currency}")
218
437
  print(f"Display Currencies: {config.display_currencies}")
219
438
  print()
@@ -22,51 +22,13 @@ from typing import Optional
22
22
  # Add parent for imports
23
23
  sys.path.insert(0, str(Path(__file__).parent))
24
24
 
25
+ from platform import IS_WINDOWS
26
+
27
+ from colors import Colors
25
28
  from cost_tracker import PRICING, CostTracker
26
29
  from currency_converter import CurrencyConverter, get_converter
27
30
 
28
31
 
29
- class Colors:
30
- """ANSI color codes for terminal output."""
31
-
32
- # Reset
33
- RESET = "\033[0m"
34
-
35
- # Regular colors
36
- BLACK = "\033[30m"
37
- RED = "\033[31m"
38
- GREEN = "\033[32m"
39
- YELLOW = "\033[33m"
40
- BLUE = "\033[34m"
41
- MAGENTA = "\033[35m"
42
- CYAN = "\033[36m"
43
- WHITE = "\033[37m"
44
-
45
- # Bold colors
46
- BOLD = "\033[1m"
47
- BOLD_RED = "\033[1;31m"
48
- BOLD_GREEN = "\033[1;32m"
49
- BOLD_YELLOW = "\033[1;33m"
50
- BOLD_BLUE = "\033[1;34m"
51
- BOLD_CYAN = "\033[1;36m"
52
- BOLD_WHITE = "\033[1;37m"
53
-
54
- # Background
55
- BG_RED = "\033[41m"
56
- BG_GREEN = "\033[42m"
57
- BG_YELLOW = "\033[43m"
58
-
59
- # Dim
60
- DIM = "\033[2m"
61
-
62
- @staticmethod
63
- def strip(text: str) -> str:
64
- """Remove ANSI codes from text."""
65
- import re
66
-
67
- return re.sub(r"\033\[[0-9;]*m", "", text)
68
-
69
-
70
32
  class CostDisplay:
71
33
  """
72
34
  Real-time cost display for terminal.
@@ -112,7 +74,7 @@ class CostDisplay:
112
74
  self.last_refresh = None
113
75
 
114
76
  # Get display currency from environment or parameter
115
- self.display_currency = display_currency or os.environ.get("COST_DISPLAY_CURRENCY")
77
+ self.display_currency = display_currency or os.getenv("COST_DISPLAY_CURRENCY")
116
78
 
117
79
  def _box_line(self, left: str, right: str, fill: str = BOX_HORIZONTAL) -> str:
118
80
  """Create a box line."""
@@ -122,7 +84,20 @@ class CostDisplay:
122
84
  """Create a content line within the box."""
123
85
  # Remove color codes for length calculation
124
86
  clean_content = Colors.strip(content)
125
- padding = self.width - 4 - len(clean_content)
87
+ max_content_width = self.width - 4 # Account for box borders and spaces
88
+
89
+ # Truncate content if too long
90
+ if len(clean_content) > max_content_width:
91
+ # Find how much to truncate (accounting for "..." suffix)
92
+ truncate_at = max_content_width - 3
93
+ if truncate_at > 0:
94
+ content = content[:truncate_at] + "..."
95
+ clean_content = Colors.strip(content)
96
+ else:
97
+ content = "..."
98
+ clean_content = "..."
99
+
100
+ padding = max(0, max_content_width - len(clean_content))
126
101
 
127
102
  if align == "center":
128
103
  left_pad = padding // 2
@@ -362,7 +337,7 @@ class CostDisplay:
362
337
 
363
338
  def clear_screen(self):
364
339
  """Clear the terminal screen."""
365
- if sys.platform == "win32":
340
+ if IS_WINDOWS:
366
341
  os.system("cls")
367
342
  else:
368
343
  os.system("clear")
@@ -389,7 +364,7 @@ class CompactCostDisplay:
389
364
  self.tracker = tracker
390
365
  self.converter = converter or get_converter()
391
366
  # Get display currency from environment or parameter
392
- self.display_currency = display_currency or os.environ.get("COST_DISPLAY_CURRENCY", "USD")
367
+ self.display_currency = display_currency or os.getenv("COST_DISPLAY_CURRENCY", "USD")
393
368
 
394
369
  def render(self) -> str:
395
370
  """Render compact display."""