tokenjam 0.3.2__tar.gz → 0.3.3__tar.gz

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 (268) hide show
  1. {tokenjam-0.3.2 → tokenjam-0.3.3}/CLAUDE.md +2 -2
  2. {tokenjam-0.3.2 → tokenjam-0.3.3}/CONTRIBUTING.md +2 -2
  3. {tokenjam-0.3.2 → tokenjam-0.3.3}/PKG-INFO +1 -1
  4. {tokenjam-0.3.2 → tokenjam-0.3.3}/pyproject.toml +1 -1
  5. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/package.json +1 -1
  6. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_tokenmaxx.py +55 -12
  7. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cost.py +15 -0
  8. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_tokenmaxx.py +143 -54
  9. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/cost.py +1 -1
  10. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/pricing/models.toml +5 -5
  11. tokenjam-0.3.2/pricing/models.toml +0 -82
  12. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/CODEOWNERS +0 -0
  13. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/ISSUE_TEMPLATE/integration_request.md +0 -0
  16. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/pull_request_template.md +0 -0
  17. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/ci.yml +0 -0
  18. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/publish-npm.yml +0 -0
  19. {tokenjam-0.3.2 → tokenjam-0.3.3}/.github/workflows/publish-pypi.yml +0 -0
  20. {tokenjam-0.3.2 → tokenjam-0.3.3}/.gitignore +0 -0
  21. {tokenjam-0.3.2 → tokenjam-0.3.3}/AGENTS.md +0 -0
  22. {tokenjam-0.3.2 → tokenjam-0.3.3}/CHANGELOG.md +0 -0
  23. {tokenjam-0.3.2 → tokenjam-0.3.3}/LICENSE +0 -0
  24. {tokenjam-0.3.2 → tokenjam-0.3.3}/Makefile +0 -0
  25. {tokenjam-0.3.2 → tokenjam-0.3.3}/README.md +0 -0
  26. {tokenjam-0.3.2 → tokenjam-0.3.3}/SECURITY.md +0 -0
  27. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/alerts.md +0 -0
  28. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/architecture.md +0 -0
  29. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/helicone.md +0 -0
  30. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/langfuse.md +0 -0
  31. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/otlp.md +0 -0
  32. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/backfill/overview.md +0 -0
  33. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/claude-code-integration.md +0 -0
  34. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/cli-reference.md +0 -0
  35. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/configuration.md +0 -0
  36. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/export.md +0 -0
  37. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/framework-support.md +0 -0
  38. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/installation.md +0 -0
  39. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/internal/specs/.gitkeep +0 -0
  40. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/nemoclaw-integration.md +0 -0
  41. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/openclaw.md +0 -0
  42. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/cache.md +0 -0
  43. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/downsize.md +0 -0
  44. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/script.md +0 -0
  45. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/optimize/trim.md +0 -0
  46. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/policy/overview.md +0 -0
  47. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/python-sdk.md +0 -0
  48. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-alerts.png +0 -0
  49. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-budget.png +0 -0
  50. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-cost.png +0 -0
  51. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-status.png +0 -0
  52. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/screenshots/tj-traces.png +0 -0
  53. {tokenjam-0.3.2 → tokenjam-0.3.3}/docs/typescript-sdk.md +0 -0
  54. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/README.md +0 -0
  55. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/_shared.py +0 -0
  56. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/budget_breach_demo.py +0 -0
  57. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/drift_demo.py +0 -0
  58. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/alerts_and_drift/sensitive_actions_demo.py +0 -0
  59. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/rag_pipeline.py +0 -0
  60. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/research_team.py +0 -0
  61. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/router_agent.py +0 -0
  62. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/agent_patterns.txt +0 -0
  63. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/cost_management.txt +0 -0
  64. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/observability.txt +0 -0
  65. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/multi/sample_docs/safety.txt +0 -0
  66. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/openclaw/README.md +0 -0
  67. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/autogen_agent.py +0 -0
  68. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/crewai_agent.py +0 -0
  69. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/langchain_agent.py +0 -0
  70. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/langgraph_agent.py +0 -0
  71. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_framework/llamaindex_agent.py +0 -0
  72. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/anthropic_agent.py +0 -0
  73. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/bedrock_agent.py +0 -0
  74. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/gemini_agent.py +0 -0
  75. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/litellm_agent.py +0 -0
  76. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/openai_agent.py +0 -0
  77. {tokenjam-0.3.2 → tokenjam-0.3.3}/examples/single_provider/openai_agents_sdk_agent.py +0 -0
  78. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/BLOG.md +0 -0
  79. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/README.md +0 -0
  80. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/hallucination-drift/scenario.py +0 -0
  81. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/BLOG.md +0 -0
  82. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/README.md +0 -0
  83. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/retry-loop/scenario.py +0 -0
  84. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/BLOG.md +0 -0
  85. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/README.md +0 -0
  86. {tokenjam-0.3.2 → tokenjam-0.3.3}/incidents/surprise-cost/scenario.py +0 -0
  87. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/README.md +0 -0
  88. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/package-lock.json +0 -0
  89. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/client.test.ts +0 -0
  90. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/client.ts +0 -0
  91. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/index.ts +0 -0
  92. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/semconv.test.ts +0 -0
  93. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/semconv.ts +0 -0
  94. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/span-builder.test.ts +0 -0
  95. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/span-builder.ts +0 -0
  96. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/src/types.ts +0 -0
  97. {tokenjam-0.3.2 → tokenjam-0.3.3}/sdk-ts/tsconfig.json +0 -0
  98. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/__init__.py +0 -0
  99. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/__init__.py +0 -0
  100. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_budget_breach.py +0 -0
  101. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_drift.py +0 -0
  102. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_loop.py +0 -0
  103. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/email_agent_normal.py +0 -0
  104. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/mock_llm.py +0 -0
  105. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/agents/test_mock_scenarios.py +0 -0
  106. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/conftest.py +0 -0
  107. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/__init__.py +0 -0
  108. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/conftest.py +0 -0
  109. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/e2e/test_real_llm.py +0 -0
  110. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/factories.py +0 -0
  111. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/helicone_real_response.json +0 -0
  112. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/langfuse_real_response.json +0 -0
  113. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/fixtures/otlp_sample.json +0 -0
  114. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/__init__.py +0 -0
  115. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_api.py +0 -0
  116. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_cli.py +0 -0
  117. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_db.py +0 -0
  118. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_demos.py +0 -0
  119. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_full_pipeline.py +0 -0
  120. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/integration/test_logs_api.py +0 -0
  121. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/manual-new-release-tests.md +0 -0
  122. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/manual-pre-release-testing.md +0 -0
  123. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/__init__.py +0 -0
  124. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_alert_rules.py +0 -0
  125. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_cost_tracking.py +0 -0
  126. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_drift_detection.py +0 -0
  127. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_ingest.py +0 -0
  128. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/synthetic/test_schema_validation.py +0 -0
  129. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/toy_agent/toy_agent.py +0 -0
  130. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/__init__.py +0 -0
  131. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_alerts.py +0 -0
  132. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_backfill.py +0 -0
  133. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cache_efficacy.py +0 -0
  134. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cache_recommend.py +0 -0
  135. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_policy.py +0 -0
  136. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_cmd_stop.py +0 -0
  137. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_compare.py +0 -0
  138. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_config.py +0 -0
  139. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_config_secret_divergence.py +0 -0
  140. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_demo_env.py +0 -0
  141. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_demo_scenarios.py +0 -0
  142. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_drift.py +0 -0
  143. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_export_claude_code.py +0 -0
  144. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_formatting.py +0 -0
  145. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_helicone.py +0 -0
  146. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_langfuse.py +0 -0
  147. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_ingest_otlp.py +0 -0
  148. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_litellm_client.py +0 -0
  149. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_litellm_integration.py +0 -0
  150. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_logs_converter.py +0 -0
  151. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_mcp_server.py +0 -0
  152. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_models.py +0 -0
  153. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_onboard_codex.py +0 -0
  154. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_onboard_daemon.py +0 -0
  155. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_openclaw_ingest.py +0 -0
  156. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_optimize.py +0 -0
  157. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_pricing_override.py +0 -0
  158. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_prompt_bloat.py +0 -0
  159. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_spans_stats_repair.py +0 -0
  160. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_time_parse.py +0 -0
  161. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_transport_401.py +0 -0
  162. {tokenjam-0.3.2 → tokenjam-0.3.3}/tests/unit/test_workflow_restructure.py +0 -0
  163. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/__init__.py +0 -0
  164. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/__init__.py +0 -0
  165. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/app.py +0 -0
  166. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/deps.py +0 -0
  167. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/middleware.py +0 -0
  168. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/__init__.py +0 -0
  169. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/agents.py +0 -0
  170. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/alerts.py +0 -0
  171. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/budget.py +0 -0
  172. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/cost.py +0 -0
  173. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/cost_compare.py +0 -0
  174. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/drift.py +0 -0
  175. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/logs.py +0 -0
  176. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/metrics.py +0 -0
  177. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/optimize.py +0 -0
  178. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/otlp.py +0 -0
  179. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/spans.py +0 -0
  180. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/status.py +0 -0
  181. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/tools.py +0 -0
  182. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/api/routes/traces.py +0 -0
  183. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/__init__.py +0 -0
  184. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_alerts.py +0 -0
  185. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_backfill.py +0 -0
  186. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_budget.py +0 -0
  187. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_cost.py +0 -0
  188. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_demo.py +0 -0
  189. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_doctor.py +0 -0
  190. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_drift.py +0 -0
  191. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_export.py +0 -0
  192. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_mcp.py +0 -0
  193. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_onboard.py +0 -0
  194. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_optimize.py +0 -0
  195. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_policy.py +0 -0
  196. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_report.py +0 -0
  197. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_serve.py +0 -0
  198. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_status.py +0 -0
  199. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_stop.py +0 -0
  200. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_tools.py +0 -0
  201. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_traces.py +0 -0
  202. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/cmd_uninstall.py +0 -0
  203. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/cli/main.py +0 -0
  204. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/__init__.py +0 -0
  205. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/alerts.py +0 -0
  206. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/api_backend.py +0 -0
  207. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/backfill.py +0 -0
  208. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/config.py +0 -0
  209. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/db.py +0 -0
  210. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/drift.py +0 -0
  211. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/export/__init__.py +0 -0
  212. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/export/claude_code.py +0 -0
  213. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest.py +0 -0
  214. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/__init__.py +0 -0
  215. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/helicone.py +0 -0
  216. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/langfuse.py +0 -0
  217. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/ingest_adapters/otlp.py +0 -0
  218. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/models.py +0 -0
  219. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/README.md +0 -0
  220. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/__init__.py +0 -0
  221. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/__init__.py +0 -0
  222. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/budget_projection.py +0 -0
  223. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/cache_efficacy.py +0 -0
  224. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/cache_recommend.py +0 -0
  225. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/model_downgrade.py +0 -0
  226. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/prompt_bloat.py +0 -0
  227. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/analyzers/workflow_restructure.py +0 -0
  228. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/registry.py +0 -0
  229. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/runner.py +0 -0
  230. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/optimize/types.py +0 -0
  231. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/pricing.py +0 -0
  232. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/retention.py +0 -0
  233. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/core/schema_validator.py +0 -0
  234. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/demo/__init__.py +0 -0
  235. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/demo/env.py +0 -0
  236. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/mcp/__init__.py +0 -0
  237. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/mcp/server.py +0 -0
  238. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/__init__.py +0 -0
  239. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/exporters.py +0 -0
  240. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/otlp_parsing.py +0 -0
  241. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/provider.py +0 -0
  242. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/otel/semconv.py +0 -0
  243. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/py.typed +0 -0
  244. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/__init__.py +0 -0
  245. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/agent.py +0 -0
  246. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/bootstrap.py +0 -0
  247. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/client.py +0 -0
  248. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/http_exporter.py +0 -0
  249. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/__init__.py +0 -0
  250. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/anthropic.py +0 -0
  251. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/autogen.py +0 -0
  252. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/base.py +0 -0
  253. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/bedrock.py +0 -0
  254. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/crewai.py +0 -0
  255. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/gemini.py +0 -0
  256. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/langchain.py +0 -0
  257. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/langgraph.py +0 -0
  258. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/litellm.py +0 -0
  259. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/llamaindex.py +0 -0
  260. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/nemoclaw.py +0 -0
  261. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/openai.py +0 -0
  262. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/integrations/openai_agents_sdk.py +0 -0
  263. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/sdk/transport.py +0 -0
  264. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/ui/index.html +0 -0
  265. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/__init__.py +0 -0
  266. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/formatting.py +0 -0
  267. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/ids.py +0 -0
  268. {tokenjam-0.3.2 → tokenjam-0.3.3}/tokenjam/utils/time_parse.py +0 -0
@@ -191,9 +191,9 @@ The Agent Incident Library at `incidents/` is separate: each scenario is a `scen
191
191
 
192
192
  ## Pricing
193
193
 
194
- Model pricing lives in `pricing/models.toml` (USD per million tokens). Structure: `[provider.model_name]` with `input_per_mtok`, `output_per_mtok`, and optional `cache_read_per_mtok`/`cache_write_per_mtok`. Unknown models fall back to default rates ($0.50/$2.00 per MTok) with a logged warning. The pricing table is LRU-cached at process startup — restart to pick up changes.
194
+ Model pricing lives in `tokenjam/pricing/models.toml` (USD per million tokens) — the packaged file `core/pricing.py` loads via `PRICING_FILE = Path(__file__).parent.parent / "pricing" / "models.toml"`. There is no repo-root `pricing/` copy (it was moved into the package in v0.1.x so it ships in the wheel; editing a repo-root file would have no runtime effect). Structure: `[provider.model_name]` with `input_per_mtok`, `output_per_mtok`, and optional `cache_read_per_mtok`/`cache_write_per_mtok`. Unknown models fall back to default rates ($0.50/$2.00 per MTok) with a logged warning. The pricing table is LRU-cached at process startup — restart to pick up changes.
195
195
 
196
- Pricing is community-maintained: submit a PR editing `pricing/models.toml` when provider prices change. No code changes needed — the file is loaded at runtime.
196
+ Pricing is community-maintained: submit a PR editing `tokenjam/pricing/models.toml` when provider prices change. No code changes needed — the file is loaded at runtime.
197
197
 
198
198
  ## CI
199
199
 
@@ -43,7 +43,7 @@ tokenjam/sdk/ @watch() decorator and provider/framework patches
43
43
  tokenjam/otel/ OTel TracerProvider and span exporter wiring
44
44
  tokenjam/utils/ Formatting, time parsing, ID generation
45
45
  sdk-ts/src/ TypeScript SDK (@tokenjam/sdk)
46
- pricing/models.toml Community-maintained model pricing — PRs welcome here
46
+ tokenjam/pricing/models.toml Community-maintained model pricing — PRs welcome here
47
47
  tests/factories.py Span factory — use this in all synthetic tests, never
48
48
  construct NormalizedSpan directly
49
49
  ```
@@ -57,7 +57,7 @@ This project was built using parallel Claude Code agents. The `.claude/` directo
57
57
 
58
58
  ## Pricing table contributions
59
59
 
60
- The file `pricing/models.toml` is intentionally community-maintained. If a model is missing or prices have changed, open a PR with the update — no issue needed, just update the TOML and verify the format matches existing entries.
60
+ The file `tokenjam/pricing/models.toml` is intentionally community-maintained. If a model is missing or prices have changed, open a PR with the update — no issue needed, just update the TOML and verify the format matches existing entries. (This is the file the cost engine loads at runtime; there is no separate repo-root copy.)
61
61
 
62
62
  ## Reporting issues
63
63
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tokenjam
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: TokenJam — local-first OTel-native observability for Autonomous AI agents
5
5
  Project-URL: Homepage, https://opencla.watch
6
6
  Project-URL: Repository, https://github.com/Metabuilder-Labs/openclawwatch
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tokenjam"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "TokenJam — local-first OTel-native observability for Autonomous AI agents"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenjam/sdk",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "TypeScript SDK for TokenJam — local-first observability for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,27 +14,54 @@ from tokenjam.core.config import ProviderBudget, TjConfig
14
14
  # ───────────────────────────── classification ─────────────────────────────
15
15
 
16
16
  def test_classify_zero_spend_is_sipper():
17
- t = _classify(0.0)
18
- assert t.label == "TokenSipper"
17
+ # Zero spend → Sipper, regardless of which path.
18
+ assert _classify(0.0).label == "TokenSipper"
19
+ assert _classify(0.0, multiplier=0.0).label == "TokenSipper"
19
20
 
20
21
 
21
- def test_classify_walks_tiers_correctly():
22
- # Each threshold should map into the next tier exactly at the boundary.
22
+ def test_classify_multiplier_path_walks_tier_boundaries():
23
+ # Multiplier-based classification the primary path for subscription users.
24
+ # Boundaries are 1× / 4× / 10× / 20× per the launch-tier spec.
23
25
  cases = [
24
- (49.99, "TokenSipper"),
25
- (50.0, "TokenModerator"),
26
- (199.99, "TokenModerator"),
27
- (200.0, "TokenMaxxer"),
28
- (499.99, "TokenMaxxer"),
29
- (500.0, "TokenChad"),
30
- (1499.99, "TokenChad"),
31
- (1500.0, "TokenGigaChad"),
26
+ (0.99, "TokenSipper"),
27
+ (1.0, "TokenModerator"),
28
+ (3.99, "TokenModerator"),
29
+ (4.0, "TokenMaxxer"),
30
+ (9.99, "TokenMaxxer"),
31
+ (10.0, "TokenChad"),
32
+ (19.99, "TokenChad"),
33
+ (20.0, "TokenGigaChad"),
34
+ (200.0, "TokenGigaChad"),
35
+ ]
36
+ for mult, expected in cases:
37
+ assert _classify(0.0, multiplier=mult).label == expected, f"failed at {mult}×"
38
+
39
+
40
+ def test_classify_absolute_path_for_api_users_walks_tier_boundaries():
41
+ # Absolute USD/mo fallback — calibrated against Max-5x = $100/mo so the
42
+ # tier names mean roughly the same thing in both paths.
43
+ cases = [
44
+ (99.99, "TokenSipper"),
45
+ (100.0, "TokenModerator"),
46
+ (399.99, "TokenModerator"),
47
+ (400.0, "TokenMaxxer"),
48
+ (999.99, "TokenMaxxer"),
49
+ (1000.0, "TokenChad"),
50
+ (1999.99, "TokenChad"),
51
+ (2000.0, "TokenGigaChad"),
32
52
  (50_000, "TokenGigaChad"),
33
53
  ]
34
54
  for spend, expected in cases:
35
55
  assert _classify(spend).label == expected, f"failed at ${spend}"
36
56
 
37
57
 
58
+ def test_classify_multiplier_overrides_absolute_when_both_provided():
59
+ # A subscription user at $50/mo on a $20 Pro plan (2.5× their plan) is
60
+ # a TokenModerator, NOT a TokenSipper — the multiplier path wins.
61
+ t = _classify(50.0, multiplier=2.5)
62
+ assert t.label == "TokenModerator"
63
+
64
+
38
65
  def test_every_tier_has_emoji_and_quip():
39
66
  # The artifact's social-shareability depends on every tier having
40
67
  # readable text — guard against a future blank entry.
@@ -95,3 +122,19 @@ def test_plan_label_and_fee_returns_none_for_unknown_or_api():
95
122
  assert _plan_label_and_fee("local") is None
96
123
  assert _plan_label_and_fee(None) is None
97
124
  assert _plan_label_and_fee("not_a_plan") is None
125
+
126
+
127
+ def test_all_anthropic_subscription_plans_produce_a_multiplier():
128
+ # Every Anthropic subscription plan in the onboard wizard must produce
129
+ # a finite multiplier, otherwise the tokenmaxx tweet hook ("Nx your
130
+ # plan") doesn't render for those users. This guards against the table
131
+ # diverging from `cmd_onboard.py::_ANTHROPIC_PLAN_CHOICES`.
132
+ for plan in ("pro", "max_5x", "max_20x"):
133
+ info = _plan_label_and_fee(plan)
134
+ assert info is not None, f"{plan!r} not in plan-fee table"
135
+ label, fee = info
136
+ assert label, f"{plan!r} missing a display label"
137
+ assert fee and fee > 0, f"{plan!r} missing a numeric monthly fee"
138
+ # Sanity-check the multiplier math the renderer does.
139
+ spend = 1000.0
140
+ assert spend / fee > 0
@@ -113,6 +113,21 @@ def test_get_rates_opus_4_8():
113
113
  assert rates.cache_write_per_mtok == 6.25
114
114
 
115
115
 
116
+ def test_calculate_cost_opus_4_5_model():
117
+ # claude-opus-4-5: input=5.00, output=25.00 (same tier as 4.6/4.7/4.8)
118
+ cost = calculate_cost("anthropic", "claude-opus-4-5", 1_000_000, 1_000_000)
119
+ assert cost == 30.0
120
+
121
+
122
+ def test_get_rates_opus_4_5():
123
+ rates = get_rates("anthropic", "claude-opus-4-5")
124
+ assert rates is not None
125
+ assert rates.input_per_mtok == 5.00
126
+ assert rates.output_per_mtok == 25.00
127
+ assert rates.cache_read_per_mtok == 0.50
128
+ assert rates.cache_write_per_mtok == 6.25
129
+
130
+
116
131
  def test_calculate_cost_openai_model():
117
132
  # gpt-4o: input=2.50, output=10.00
118
133
  cost = calculate_cost("openai", "gpt-4o", 500_000, 100_000)
@@ -20,13 +20,17 @@ from dataclasses import dataclass
20
20
 
21
21
  import click
22
22
 
23
- from tokenjam.utils.formatting import console, format_cost
23
+ from tokenjam.utils.formatting import console, format_cost # noqa: F401 (kept for back-compat imports)
24
24
  from tokenjam.utils.time_parse import parse_since, utcnow
25
25
 
26
26
 
27
- # Tier table — monthly USD spend thresholds → (label, one-liner).
28
- # Calibrated for individual coding-agent users. Order matters: walked
29
- # bottom-up, first matching threshold wins.
27
+ # Tier table — multiplier thresholds (×plan-fee) → (label, one-liner).
28
+ # The `threshold` field is interpreted as a multiplier when the user has a
29
+ # subscription plan declared, or as an absolute USD/mo amount when they
30
+ # don't (API users, no plan set). The fallback thresholds in _SPEND_TIERS
31
+ # below mirror the multiplier ladder calibrated against the Max-5x plan
32
+ # ($100/mo) so the tier names mean roughly the same thing across paths.
33
+ # Order matters: classify walks high-to-low; first match wins.
30
34
  @dataclass(frozen=True)
31
35
  class Tier:
32
36
  threshold: float
@@ -36,18 +40,34 @@ class Tier:
36
40
 
37
41
 
38
42
  _TIERS: list[Tier] = [
39
- Tier(0, "TokenSipper", "💧", "Are you even using AI?"),
40
- Tier(50, "TokenModerator", "🥱", "Mostly reasonable. Try harder."),
41
- Tier(200, "TokenMaxxer", "💸", "You're paying Anthropic's rent."),
42
- Tier(500, "TokenChad", "🔥", "You're paying their interns' rent too."),
43
- Tier(1500, "TokenGigaChad", "🔥🔥", "Touch grass. Then run `tj optimize`."),
43
+ Tier(0, "TokenSipper", "💧", "Are you even using AI?"),
44
+ Tier(1, "TokenModerator", "🥱", "Mostly reasonable. Try harder."),
45
+ Tier(4, "TokenMaxxer", "💸", "You're paying Anthropic's rent."),
46
+ Tier(10, "TokenChad", "🔥", "You're paying their interns' rent too."),
47
+ Tier(20, "TokenGigaChad", "🔥🔥", "Touch grass. Then run `tj optimize`."),
44
48
  ]
45
49
 
50
+ # Absolute USD/mo fallback for users without a subscription plan (API users).
51
+ # Calibrated against Max-5x = $100/mo: each threshold is the multiplier × $100.
52
+ # This way a $400/mo API user and a 4× Pro/Max-5x/Max-20x user both end up
53
+ # in TokenMaxxer — the tier name reflects "shocking spend" in either world.
54
+ _SPEND_TIER_THRESHOLDS_USD: list[float] = [0, 100, 400, 1000, 2000]
46
55
 
47
- def _classify(monthly_spend: float) -> Tier:
48
- """Walk tiers high-to-low; first threshold the spend exceeds wins."""
49
- for tier in reversed(_TIERS):
50
- if monthly_spend >= tier.threshold:
56
+
57
+ def _classify(monthly_spend: float, multiplier: float | None = None) -> Tier:
58
+ """
59
+ Pick a tier. Prefer the multiplier path when the user has a subscription
60
+ plan with a declared fee; fall back to absolute monthly spend when not.
61
+ Walks tiers high-to-low; first matching threshold wins.
62
+ """
63
+ if multiplier is not None:
64
+ for tier in reversed(_TIERS):
65
+ if multiplier >= tier.threshold:
66
+ return tier
67
+ return _TIERS[0]
68
+ # API / no-plan path — map onto the same tier labels via absolute USD.
69
+ for tier, threshold_usd in zip(reversed(_TIERS), reversed(_SPEND_TIER_THRESHOLDS_USD)):
70
+ if monthly_spend >= threshold_usd:
51
71
  return tier
52
72
  return _TIERS[0]
53
73
 
@@ -86,7 +106,7 @@ def cmd_tokenmaxx(ctx: click.Context, since: str, output_json: bool) -> None:
86
106
  if plan_info and plan_info[1]:
87
107
  multiplier = monthly_spend / plan_info[1]
88
108
 
89
- tier = _classify(monthly_spend)
109
+ tier = _classify(monthly_spend, multiplier)
90
110
 
91
111
  if output_json:
92
112
  click.echo(json.dumps({
@@ -201,6 +221,28 @@ def _plan_label_and_fee(plan_tier: str | None) -> tuple[str, float | None] | Non
201
221
 
202
222
  # ───────────────────────────── rendering ──────────────────────────────────
203
223
 
224
+ def _fmt_spend(usd: float) -> str:
225
+ """Spend / savings: always 2 decimals — readable, screenshot-friendly.
226
+
227
+ The default `format_cost` helper uses 4 decimals (precision for the cost
228
+ engine internals); for the tokenmaxx social artifact we want $4044.57,
229
+ not $4044.5774.
230
+ """
231
+ return f"${usd:.2f}"
232
+
233
+
234
+ def _fmt_fee(usd: float) -> str:
235
+ """Plan fee: drop the decimals when the fee is a round dollar.
236
+
237
+ Anthropic / OpenAI subscription tiers (Pro $20, Max-5x $100, Max-20x $200)
238
+ are all whole-dollar — `$100.00` reads worse than `$100`. Falls back to
239
+ 2 decimals for anything fractional.
240
+ """
241
+ if usd == int(usd):
242
+ return f"${int(usd)}"
243
+ return f"${usd:.2f}"
244
+
245
+
204
246
  def _render(
205
247
  *, tier: Tier, spend_usd: float, monthly_spend: float,
206
248
  window_days: int, sessions: int,
@@ -208,7 +250,12 @@ def _render(
208
250
  multiplier: float | None,
209
251
  savings_usd: float,
210
252
  ) -> None:
211
- """Big-headline render. Designed to be a clean screenshot artifact."""
253
+ """
254
+ Big-headline render. Designed to be a clean screenshot artifact:
255
+ bordered Panel with a heading, the tier callout up top, the spend
256
+ breakdown in the middle, the action line at the bottom, and the
257
+ share prompt OUTSIDE the panel.
258
+ """
212
259
  if sessions == 0:
213
260
  console.print(
214
261
  "\n[yellow]No usage data found.[/yellow]\n"
@@ -217,54 +264,96 @@ def _render(
217
264
  )
218
265
  return
219
266
 
220
- # Banner the headline shareable line.
221
- console.print()
222
- console.print(f" {tier.emoji} [bold]You're a {tier.label}.[/bold]")
223
- console.print(f" [dim italic]\"{tier.quip}\"[/dim italic]")
224
- console.print()
225
-
226
- # Spend breakdown — what produced the tier.
267
+ # Build the inside-the-panel content as one rich Text/markup string.
268
+ # Rich Panel doesn't have native sub-spacing primitives, so we hand-pad
269
+ # with newlines to get the visual rhythm we want in the screenshot.
270
+ from rich.console import Group
271
+ from rich.panel import Panel
272
+ from rich.text import Text
273
+ from rich.align import Align
274
+
275
+ # Tier callout — the headline, plus a larger non-dim quip (no quotes)
276
+ # with `tj optimize` highlighted green-bold when it appears in the quip.
277
+ headline = Text()
278
+ headline.append(f"{tier.emoji} ", style="")
279
+ headline.append("You're a ", style="bold")
280
+ headline.append(tier.label, style="bold")
281
+ headline.append(".", style="bold")
282
+
283
+ quip_text = Text()
284
+ # Walk the quip and recolor any `tj optimize` backtick-wrapped token green.
285
+ # Rich doesn't auto-parse backticks, so we do it manually with split.
286
+ parts = tier.quip.split("`tj optimize`")
287
+ for i, p in enumerate(parts):
288
+ if p:
289
+ quip_text.append(p, style="")
290
+ if i < len(parts) - 1:
291
+ quip_text.append("tj optimize", style="bold green")
292
+
293
+ # The spend / multiplier block — same content as before but with the
294
+ # cleaner formatters and a slightly tighter line structure.
295
+ body = Text()
227
296
  actual_label = f"last {window_days}d" if window_days < 30 else "last 30d"
228
- if window_days == 30:
229
- console.print(
230
- f" [bold]{format_cost(spend_usd)}[/bold] in {actual_label} "
231
- f"across [bold]{sessions}[/bold] sessions."
232
- )
233
- else:
234
- console.print(
235
- f" [bold]{format_cost(spend_usd)}[/bold] in {actual_label} "
236
- f"across [bold]{sessions}[/bold] sessions "
237
- f"(≈ [bold]{format_cost(monthly_spend)}/mo[/bold] at this rate)."
238
- )
297
+ body.append(_fmt_spend(spend_usd), style="bold")
298
+ body.append(f" in {actual_label} across ")
299
+ body.append(str(sessions), style="bold")
300
+ body.append(" sessions.")
301
+ if window_days != 30:
302
+ body.append(" (≈ ", style="dim")
303
+ body.append(_fmt_spend(monthly_spend), style="bold")
304
+ body.append("/mo at this rate)", style="dim")
239
305
 
240
- # Plan multiplier — the punchline.
241
306
  if plan_info and multiplier:
242
307
  plan_label, fee = plan_info
243
- console.print(
244
- f" That's [bold]{multiplier:.1f}×[/bold] your "
245
- f"[bold]{plan_label}[/bold] cost ({format_cost(fee)}/mo flat)."
246
- )
308
+ body.append("\nThat's ")
309
+ body.append(f"{multiplier:.1f}×", style="bold")
310
+ body.append(" your ")
311
+ body.append(plan_label, style="bold")
312
+ body.append(f" cost ({_fmt_fee(fee)}/mo flat).")
247
313
  elif plan_info:
248
314
  plan_label, _ = plan_info
249
- console.print(f" Plan: [bold]{plan_label}[/bold].")
250
-
251
- console.print()
252
-
253
- # The actionthe part that makes this a tool, not a flex.
315
+ body.append("\nPlan: ")
316
+ body.append(plan_label, style="bold")
317
+ body.append(".")
318
+
319
+ # Action linesavings recoverable, or fall through to "no obvious
320
+ # savings yet". `tj optimize` rendered green-bold either way so the
321
+ # eye lands on the verb.
322
+ action = Text("💡 ")
254
323
  if savings_usd > 0:
255
- console.print(
256
- f" 💡 [bold]{format_cost(savings_usd)}/mo[/bold] of that looks "
257
- f"recoverable. Run [bold]tj optimize[/bold] to see candidates."
258
- )
324
+ action.append(_fmt_spend(savings_usd) + "/mo", style="bold")
325
+ action.append(" of that looks recoverable. Run ")
326
+ action.append("tj optimize", style="bold green")
327
+ action.append(" to see candidates.")
259
328
  else:
260
- console.print(
261
- " 💡 No obvious savings flagged yet — run [bold]tj optimize[/bold] "
262
- "for the full report once you have more data."
263
- )
264
- console.print()
329
+ action.append("No obvious savings flagged yet — run ")
330
+ action.append("tj optimize", style="bold green")
331
+ action.append(" for the full report once you have more data.")
332
+
333
+ # Compose with deliberate vertical spacing.
334
+ panel_body = Group(
335
+ headline,
336
+ Text(""), # blank line under headline
337
+ Align.left(quip_text),
338
+ Text(""), # blank line under quip
339
+ body,
340
+ Text(""), # blank line before action
341
+ action,
342
+ )
265
343
 
266
- # Subtle share prompt.
344
+ console.print()
345
+ console.print(Panel(
346
+ panel_body,
347
+ title="[bold]TokenJam TokenMaxxing Report[/bold]",
348
+ title_align="left",
349
+ border_style="dim",
350
+ padding=(1, 2),
351
+ ))
352
+
353
+ # Share prompt — outside the panel, teal, points at the brand handle so
354
+ # the social mechanic routes to a real account we can amplify from.
267
355
  console.print(
268
- " [dim]Share your tier: screenshot the above and post with #tokenmaxx[/dim]"
356
+ " [cyan]Share your tier: screenshot the above and tag "
357
+ "[bold]@tokenjamdev[/bold][/cyan]"
269
358
  )
270
359
  console.print()
@@ -32,7 +32,7 @@ def calculate_cost(
32
32
  if rates is None:
33
33
  logger.warning(
34
34
  "No pricing data for %s/%s — using default rates. "
35
- "Add to pricing/models.toml to get accurate costs.",
35
+ "Add to tokenjam/pricing/models.toml to get accurate costs.",
36
36
  provider, model,
37
37
  )
38
38
  rates = ModelRates(
@@ -1,4 +1,4 @@
1
- # pricing/models.toml
1
+ # tokenjam/pricing/models.toml
2
2
  # Prices in USD per million tokens.
3
3
  # Submit a PR when provider prices change.
4
4
 
@@ -15,10 +15,10 @@ cache_read_per_mtok = 0.50
15
15
  cache_write_per_mtok = 6.25
16
16
 
17
17
  [anthropic.claude-opus-4-5]
18
- input_per_mtok = 15.00
19
- output_per_mtok = 75.00
20
- cache_read_per_mtok = 1.50
21
- cache_write_per_mtok = 18.75
18
+ input_per_mtok = 5.00
19
+ output_per_mtok = 25.00
20
+ cache_read_per_mtok = 0.50
21
+ cache_write_per_mtok = 6.25
22
22
 
23
23
  [anthropic.claude-opus-4-6]
24
24
  input_per_mtok = 5.00
@@ -1,82 +0,0 @@
1
- # pricing/models.toml
2
- # Prices in USD per million tokens.
3
- # Submit a PR when provider prices change.
4
-
5
- [anthropic.claude-opus-4-8]
6
- input_per_mtok = 5.00
7
- output_per_mtok = 25.00
8
- cache_read_per_mtok = 0.50
9
- cache_write_per_mtok = 6.25
10
-
11
- [anthropic.claude-opus-4-7]
12
- input_per_mtok = 5.00
13
- output_per_mtok = 25.00
14
- cache_read_per_mtok = 0.50
15
- cache_write_per_mtok = 6.25
16
-
17
- [anthropic.claude-opus-4-6]
18
- input_per_mtok = 5.00
19
- output_per_mtok = 25.00
20
- cache_read_per_mtok = 0.50
21
- cache_write_per_mtok = 6.25
22
-
23
- [anthropic.claude-sonnet-4-6]
24
- input_per_mtok = 3.00
25
- output_per_mtok = 15.00
26
- cache_read_per_mtok = 0.30
27
- cache_write_per_mtok = 3.75
28
-
29
- [anthropic.claude-haiku-4-5]
30
- input_per_mtok = 0.80
31
- output_per_mtok = 4.00
32
- cache_read_per_mtok = 0.08
33
- cache_write_per_mtok = 1.00
34
-
35
- [openai.gpt-4o]
36
- input_per_mtok = 2.50
37
- output_per_mtok = 10.00
38
-
39
- [openai.gpt-4o-mini]
40
- input_per_mtok = 0.15
41
- output_per_mtok = 0.60
42
-
43
- [openai.o3]
44
- input_per_mtok = 10.00
45
- output_per_mtok = 40.00
46
-
47
- [openai.o4-mini]
48
- input_per_mtok = 1.10
49
- output_per_mtok = 4.40
50
-
51
- [google.gemini-2-5-pro]
52
- input_per_mtok = 1.25
53
- output_per_mtok = 10.00
54
-
55
- [google.gemini-2-5-flash]
56
- input_per_mtok = 0.15
57
- output_per_mtok = 0.60
58
-
59
- [aws.us-amazon-nova-pro-v1]
60
- input_per_mtok = 0.80
61
- output_per_mtok = 3.20
62
-
63
- [aws.us-amazon-nova-lite-v1]
64
- input_per_mtok = 0.06
65
- output_per_mtok = 0.24
66
-
67
- # OpenAI-compatible providers (Groq, Together, Fireworks, xAI, Azure OpenAI)
68
- # use patch_openai() with a custom base_url — add their model names here
69
- # as they are encountered.
70
-
71
- [groq.llama-3-3-70b-versatile]
72
- input_per_mtok = 0.59
73
- output_per_mtok = 0.79
74
-
75
- [xai.grok-3]
76
- input_per_mtok = 3.00
77
- output_per_mtok = 15.00
78
-
79
- # HUD managed inference (inference.hud.ai)
80
- [hud.claude-sonnet-4-5]
81
- input_per_mtok = 3.00
82
- output_per_mtok = 15.00
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes