@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.
- package/.claude/commands/agent.md +1 -1
- package/.claude/commands/bugfix.md +21 -0
- package/.claude/commands/checkpoint.md +0 -1
- package/.claude/commands/collab.md +0 -1
- package/.claude/commands/costs.md +88 -18
- package/.claude/commands/devflow.md +26 -0
- package/.claude/commands/handoff.md +0 -1
- package/.claude/commands/init.md +287 -0
- package/.claude/commands/memory.md +0 -1
- package/.claude/commands/pair.md +0 -1
- package/.claude/commands/review.md +27 -0
- package/.claude/commands/route.md +0 -1
- package/.claude/commands/swarm.md +0 -1
- package/.claude/commands/validate.md +55 -0
- package/.claude/hooks/session-notification.sh +44 -0
- package/.claude/hooks/session-startup.sh +427 -0
- package/.claude/hooks/session-stop.sh +38 -0
- package/.claude/hooks/session_tracker.py +272 -0
- package/.claude/settings.json +38 -0
- package/.claude/skills/costs/SKILL.md +156 -0
- package/.claude/skills/validate/SKILL.md +101 -0
- package/CHANGELOG.md +243 -0
- package/README.md +207 -10
- package/bin/devflow-install.js +2 -1
- package/bin/devflow.js +4 -0
- package/lib/constants.js +0 -1
- package/lib/exec-python.js +1 -1
- package/package.json +1 -1
- package/tooling/.automation/.checkpoint_lock +1 -0
- package/tooling/.automation/agents/architect.md +19 -0
- package/tooling/.automation/agents/ba.md +19 -0
- package/tooling/.automation/agents/maintainer.md +19 -0
- package/tooling/.automation/agents/pm.md +19 -0
- package/tooling/.automation/agents/reviewer.md +1 -1
- package/tooling/.automation/agents/writer.md +19 -0
- package/tooling/.automation/benchmarks/benchmark_20251230_100119.json +314 -0
- package/tooling/.automation/benchmarks/benchmark_20251230_100216.json +314 -0
- package/tooling/.automation/costs/config.json +31 -0
- package/tooling/.automation/costs/sessions/2025-12-29_20251229_164128.json +22 -0
- package/tooling/.automation/memory/knowledge/kg_integration-test.json +707 -1
- package/tooling/.automation/memory/knowledge/kg_test-story.json +3273 -2
- package/tooling/.automation/memory/shared/shared_integration-test.json +181 -1
- package/tooling/.automation/memory/shared/shared_test-story.json +721 -1
- package/tooling/.automation/memory/shared/shared_test.json +1254 -0
- package/tooling/.automation/memory/shared/shared_validation-check.json +227 -0
- package/tooling/.automation/overrides/templates/architect/cloud-native.yaml +5 -5
- package/tooling/.automation/overrides/templates/architect/enterprise-architect.yaml +23 -5
- package/tooling/.automation/overrides/templates/architect/pragmatic-minimalist.yaml +24 -6
- package/tooling/.automation/overrides/templates/ba/agile-storyteller.yaml +4 -4
- package/tooling/.automation/overrides/templates/ba/domain-expert.yaml +4 -4
- package/tooling/.automation/overrides/templates/ba/requirements-engineer.yaml +4 -4
- package/tooling/.automation/overrides/templates/dev/performance-engineer.yaml +18 -0
- package/tooling/.automation/overrides/templates/dev/rapid-prototyper.yaml +19 -1
- package/tooling/.automation/overrides/templates/dev/security-focused.yaml +18 -0
- package/tooling/.automation/overrides/templates/dev/user-advocate.yaml +54 -0
- package/tooling/.automation/overrides/templates/maintainer/devops-maintainer.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/legacy-steward.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/oss-maintainer.yaml +4 -4
- package/tooling/.automation/overrides/templates/maintainer/reliability-engineer.yaml +55 -0
- package/tooling/.automation/overrides/templates/pm/agile-pm.yaml +4 -4
- package/tooling/.automation/overrides/templates/pm/hybrid-delivery.yaml +3 -3
- package/tooling/.automation/overrides/templates/pm/traditional-pm.yaml +4 -4
- package/tooling/.automation/overrides/templates/reviewer/quick-sanity.yaml +18 -0
- package/tooling/.automation/overrides/templates/reviewer/thorough-critic.yaml +18 -0
- package/tooling/.automation/overrides/templates/sm/agile-coach.yaml +2 -2
- package/tooling/.automation/overrides/templates/sm/startup-pm.yaml +3 -3
- package/tooling/.automation/overrides/templates/writer/api-documentarian.yaml +5 -5
- package/tooling/.automation/overrides/templates/writer/docs-as-code.yaml +4 -4
- package/tooling/.automation/overrides/templates/writer/user-guide-author.yaml +5 -5
- package/tooling/.automation/validation/history/2025-12-29_val_002a28c1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_01273bb1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_03369914.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_07a449ba.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_0df1f0a2.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_10ff3d34.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_110771d7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_13f3a7f9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_17ba9d21.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_22247089.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_227ea6a4.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2335d5ae.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_246824bb.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_28b4b9cd.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2abd12cc.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2c801b2f.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2c8cfa8e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_2ce76eb0.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_30351948.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_30eb7229.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_34df0e77.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_376e4d6a.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3a4e8a1a.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3b77a628.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_3ea4e1cf.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_44aacdb4.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_457ddfa8.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_45af6238.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_4735dba1.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_486b203c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_49dc56cd.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_4d863d6d.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_5149a808.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_52e0bb43.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_585d6319.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_5b2d859a.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_635a7081.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_64df4905.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_70634cee.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_714553f9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_7f7bfdbf.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_7faad91d.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_81821f8f.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8249f3c9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8422b50f.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8446c134.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_879f4e26.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8b6d5bd7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_8c5cd787.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_91d20bc7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_958a12b7.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_95d91108.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_980dbb74.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9e40c79b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9f499b7c.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_9f7c3b57.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a30d5bd4.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a6eb09c7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_a86f7b83.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ad5347e1.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_b0a5a993.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_bcb0192e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_bf3c9aaa.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c461ff88.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c4f4e258.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c7f0fa6d.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_c911b0e6.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cc581964.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cdd5a33b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_cfd42495.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d1c7a4ee.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d2280d0e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d2a6ff69.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d8c53ab2.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d9c1247a.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_d9d58569.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_dabb4fd9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_dd8fe359.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_decdffc9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_e3a95476.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_e776dfca.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ea70969f.json +59 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ef41ea95.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_f384f9b1.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_f8adc38c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fa40b69e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fc538d54.json +41 -0
- package/tooling/.automation/validation/history/2025-12-29_val_fe814665.json +32 -0
- package/tooling/.automation/validation/history/2025-12-29_val_ffea4b12.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_02d001e5.json +59 -0
- package/tooling/.automation/validation/history/2025-12-30_val_0b8966dc.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_15455fbf.json +59 -0
- package/tooling/.automation/validation/history/2025-12-30_val_157e34b9.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_28d1d933.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_3442a52c.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_37f1ce1e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_4f1d8a93.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_56ff1de3.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_664fd4e2.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_66afb0a7.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_7634663c.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_8ea830c3.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_998957c2.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_a52177db.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_a5b65a63.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ae391d0e.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_c7895339.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ca416593.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_cee19422.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ddd4f4e6.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_f2e1394b.json +32 -0
- package/tooling/.automation/validation/history/2025-12-30_val_f4a7fa06.json +41 -0
- package/tooling/.automation/validation/history/2025-12-30_val_ffea3369.json +32 -0
- package/tooling/.automation/validation-config.yaml +103 -0
- package/tooling/completions/DevflowCompletion.ps1 +21 -21
- package/tooling/completions/_run-story +3 -3
- package/tooling/completions/run-story-completion.bash +8 -8
- package/tooling/docs/DOC-STANDARD.md +14 -14
- package/tooling/docs/templates/migration-spec.md +4 -4
- package/tooling/scripts/context_checkpoint.py +5 -15
- package/tooling/scripts/cost_dashboard.py +610 -13
- package/tooling/scripts/create-persona.py +1 -12
- package/tooling/scripts/create-persona.sh +44 -44
- package/tooling/scripts/lib/__init__.py +12 -1
- package/tooling/scripts/lib/agent_handoff.py +11 -2
- package/tooling/scripts/lib/agent_router.py +31 -10
- package/tooling/scripts/lib/colors.py +106 -0
- package/tooling/scripts/lib/context_monitor.py +766 -0
- package/tooling/scripts/lib/cost_config.py +229 -10
- package/tooling/scripts/lib/cost_display.py +20 -45
- package/tooling/scripts/lib/cost_tracker.py +462 -15
- package/tooling/scripts/lib/currency_converter.py +28 -5
- package/tooling/scripts/lib/pair_programming.py +102 -3
- package/tooling/scripts/lib/personality_system.py +949 -0
- package/tooling/scripts/lib/platform.py +55 -0
- package/tooling/scripts/lib/shared_memory.py +9 -3
- package/tooling/scripts/lib/swarm_orchestrator.py +514 -75
- package/tooling/scripts/lib/validation_loop.py +1014 -0
- package/tooling/scripts/memory_summarize.py +9 -2
- package/tooling/scripts/new-doc.py +2 -9
- package/tooling/scripts/personalize_agent.py +1 -12
- package/tooling/scripts/rollback-migration.sh +60 -60
- package/tooling/scripts/run-collab.ps1 +16 -16
- package/tooling/scripts/run-collab.py +88 -53
- package/tooling/scripts/run-collab.sh +4 -4
- package/tooling/scripts/run-story.py +278 -20
- package/tooling/scripts/run-story.sh +3 -3
- package/tooling/scripts/setup-checkpoint-service.py +2 -9
- package/tooling/scripts/tech-debt-tracker.py +1 -12
- package/tooling/scripts/test_adversarial_swarm.py +452 -0
- package/tooling/scripts/validate-overrides.py +1 -10
- package/tooling/scripts/validate-overrides.sh +40 -40
- package/tooling/scripts/validate_loop.py +162 -0
- package/tooling/scripts/validate_setup.py +2 -30
- 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 =
|
|
125
|
+
config.budget_context = _safe_float("MAX_BUDGET_CONTEXT", config.budget_context)
|
|
67
126
|
if os.getenv("MAX_BUDGET_DEV"):
|
|
68
|
-
config.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 =
|
|
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 =
|
|
133
|
+
config.warning_percent = _safe_int("COST_WARNING_PERCENT", config.warning_percent)
|
|
75
134
|
if os.getenv("COST_CRITICAL_PERCENT"):
|
|
76
|
-
config.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"] =
|
|
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"] =
|
|
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"] =
|
|
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
|
|
131
|
-
print(f"Warning:
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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."""
|