@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.
- package/.claude/commands/agent.md +1 -1
- package/.claude/commands/brainstorm.md +28 -0
- 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 +383 -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/brainstorm/SKILL.md +531 -0
- package/.claude/skills/costs/SKILL.md +156 -0
- package/.claude/skills/validate/SKILL.md +101 -0
- package/CHANGELOG.md +284 -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 +738 -1
- package/tooling/.automation/memory/knowledge/kg_test-story.json +3381 -2
- package/tooling/.automation/memory/shared/shared_integration-test.json +193 -1
- package/tooling/.automation/memory/shared/shared_test-story.json +757 -1
- package/tooling/.automation/memory/shared/shared_test.json +1332 -0
- package/tooling/.automation/memory/shared/shared_validation-check.json +240 -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/history/2026-01-03_val_1287a74c.json +41 -0
- package/tooling/.automation/validation/history/2026-01-03_val_3b24071f.json +32 -0
- package/tooling/.automation/validation/history/2026-01-03_val_44d77573.json +32 -0
- package/tooling/.automation/validation/history/2026-01-03_val_5b31dc51.json +32 -0
- package/tooling/.automation/validation/history/2026-01-03_val_74267244.json +32 -0
- package/tooling/.automation/validation/history/2026-01-03_val_8b2d95c7.json +59 -0
- package/tooling/.automation/validation/history/2026-01-03_val_d875b297.json +41 -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/stories/.gitkeep +0 -0
- package/tooling/docs/templates/brainstorm-guide.md +314 -0
- package/tooling/docs/templates/migration-spec.md +4 -4
- package/tooling/docs/templates/story.md +66 -0
- 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
|
@@ -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
|
-
|
|
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
|
|
10
|
-
python cost_dashboard.py --history 10
|
|
11
|
-
python cost_dashboard.py --summary
|
|
12
|
-
python cost_dashboard.py --
|
|
13
|
-
python cost_dashboard.py --
|
|
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
|
|
552
|
-
python cost_dashboard.py --history 10
|
|
553
|
-
python cost_dashboard.py --summary
|
|
554
|
-
python cost_dashboard.py --
|
|
555
|
-
python cost_dashboard.py --
|
|
556
|
-
python cost_dashboard.py --
|
|
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.
|
|
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:
|