atlasagent-cli 0.5.0__py3-none-any.whl
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.
- _atlas_version.py +31 -0
- agent/__init__.py +6 -0
- agent/account_usage.py +326 -0
- agent/anthropic_adapter.py +1973 -0
- agent/auxiliary_client.py +4169 -0
- agent/bedrock_adapter.py +1276 -0
- agent/codex_responses_adapter.py +999 -0
- agent/context_compressor.py +1479 -0
- agent/context_engine.py +206 -0
- agent/context_references.py +518 -0
- agent/copilot_acp_client.py +646 -0
- agent/credential_pool.py +1603 -0
- agent/credential_sources.py +418 -0
- agent/curator.py +1674 -0
- agent/curator_backup.py +693 -0
- agent/display.py +1004 -0
- agent/error_classifier.py +1036 -0
- agent/file_safety.py +111 -0
- agent/gemini_cloudcode_adapter.py +903 -0
- agent/gemini_native_adapter.py +965 -0
- agent/gemini_schema.py +99 -0
- agent/google_code_assist.py +452 -0
- agent/google_oauth.py +1061 -0
- agent/i18n.py +233 -0
- agent/image_gen_provider.py +242 -0
- agent/image_gen_registry.py +120 -0
- agent/image_routing.py +301 -0
- agent/insights.py +930 -0
- agent/lmstudio_reasoning.py +48 -0
- agent/manual_compression_feedback.py +49 -0
- agent/memory_manager.py +555 -0
- agent/memory_provider.py +279 -0
- agent/model_metadata.py +1483 -0
- agent/models_dev.py +635 -0
- agent/moonshot_schema.py +231 -0
- agent/nous_rate_guard.py +325 -0
- agent/onboarding.py +193 -0
- agent/prompt_builder.py +1186 -0
- agent/prompt_caching.py +72 -0
- agent/rate_limit_tracker.py +246 -0
- agent/redact.py +403 -0
- agent/retry_utils.py +57 -0
- agent/shell_hooks.py +836 -0
- agent/skill_commands.py +501 -0
- agent/skill_preprocessing.py +131 -0
- agent/skill_utils.py +473 -0
- agent/subdirectory_hints.py +224 -0
- agent/think_scrubber.py +386 -0
- agent/title_generator.py +171 -0
- agent/tool_guardrails.py +455 -0
- agent/trajectory.py +56 -0
- agent/transports/__init__.py +68 -0
- agent/transports/anthropic.py +179 -0
- agent/transports/base.py +89 -0
- agent/transports/bedrock.py +154 -0
- agent/transports/chat_completions.py +597 -0
- agent/transports/codex.py +246 -0
- agent/transports/types.py +162 -0
- agent/usage_pricing.py +721 -0
- atlas_agent_internal/__init__.py +33 -0
- atlas_agent_internal/dispatch.py +122 -0
- atlas_agent_internal/init_cmd.py +447 -0
- atlas_cli/__init__.py +56 -0
- atlas_cli/cli.py +700 -0
- atlas_cli/client.py +431 -0
- atlas_cli/config.py +132 -0
- atlasagent_cli-0.5.0.dist-info/METADATA +334 -0
- atlasagent_cli-0.5.0.dist-info/RECORD +411 -0
- atlasagent_cli-0.5.0.dist-info/WHEEL +5 -0
- atlasagent_cli-0.5.0.dist-info/entry_points.txt +7 -0
- atlasagent_cli-0.5.0.dist-info/licenses/LICENSE +21 -0
- atlasagent_cli-0.5.0.dist-info/top_level.txt +29 -0
- batch_runner.py +1287 -0
- billing/__init__.py +48 -0
- billing/cost_ceiling.py +231 -0
- billing/stripe_client.py +284 -0
- billing/tiers.py +168 -0
- billing/webhook.py +240 -0
- brain/__init__.py +45 -0
- brain/claude_mem_adapter.py +217 -0
- brain/cross_tenant_drift_scan.py +311 -0
- brain/doctrine_library.py +293 -0
- brain/mail_routes.py +413 -0
- brain/mail_store.py +498 -0
- brain/mr64_substrate.py +361 -0
- brain/mycelium_adapter.py +309 -0
- brain/notification_routes.py +231 -0
- brain/notification_store.py +429 -0
- brain/resend_client.py +191 -0
- brain/sangha_identifier.py +274 -0
- brain/server.py +3307 -0
- brain/test_tenant_reaper.py +214 -0
- cli.py +12574 -0
- cortex/__init__.py +68 -0
- cortex/anti_catastrophic_reframe.py +246 -0
- cortex/apex_design_drift.py +94 -0
- cortex/architectural_tranche_check.py +310 -0
- cortex/architecture_review_gate.py +250 -0
- cortex/atomic_decompose.py +368 -0
- cortex/background_subagent_default.py +119 -0
- cortex/brief_diagnose_widen.py +216 -0
- cortex/bushido_pre_claim.py +396 -0
- cortex/canonical_touch_detector.py +188 -0
- cortex/credentials_search_gate.py +270 -0
- cortex/critical_facts_stale_warn.py +138 -0
- cortex/destructive_command_guard.py +319 -0
- cortex/diagnostic_before_destructive.py +245 -0
- cortex/dispatch_dedup.py +246 -0
- cortex/dispatch_multi_component_check.py +283 -0
- cortex/dispatch_or_die.py +338 -0
- cortex/drift_detector_session.py +391 -0
- cortex/e2e_evidence_pre_ship.py +320 -0
- cortex/empirical_verify_gate.py +878 -0
- cortex/evidence_ratio_gate.py +201 -0
- cortex/factual_claim_audit.py +316 -0
- cortex/hook_block_retry_gate.py +236 -0
- cortex/idle_work_queue_aware.py +382 -0
- cortex/memory_citation_empirical_verify.py +154 -0
- cortex/onboarding_ritual_gate.py +137 -0
- cortex/operator_action_intent_detector.py +343 -0
- cortex/operator_dispatch_directive.py +445 -0
- cortex/operator_tranche_gate.py +274 -0
- cortex/output_style_audit.py +842 -0
- cortex/per_tenant_meta_judge.py +343 -0
- cortex/positive_negative_check.py +155 -0
- cortex/post_session_memory_update.py +97 -0
- cortex/post_write_file_verify.py +330 -0
- cortex/project_files_freshness.py +428 -0
- cortex/rq_reco_not_directive.py +290 -0
- cortex/sqlite_state_store.py +251 -0
- cortex/state_store.py +214 -0
- cortex/tenant_context.py +270 -0
- cortex/tenant_identity.py +509 -0
- cortex/tenant_memory_loader.py +98 -0
- cortex/tranche_allow.py +275 -0
- hermes_cli/__init__.py +47 -0
- hermes_cli/_parser.py +376 -0
- hermes_cli/auth.py +5341 -0
- hermes_cli/auth_commands.py +718 -0
- hermes_cli/azure_detect.py +300 -0
- hermes_cli/backup.py +939 -0
- hermes_cli/banner.py +635 -0
- hermes_cli/browser_connect.py +138 -0
- hermes_cli/callbacks.py +242 -0
- hermes_cli/checkpoints.py +244 -0
- hermes_cli/claw.py +803 -0
- hermes_cli/cli_output.py +78 -0
- hermes_cli/clipboard.py +481 -0
- hermes_cli/codex_models.py +177 -0
- hermes_cli/colors.py +38 -0
- hermes_cli/commands.py +1713 -0
- hermes_cli/completion.py +315 -0
- hermes_cli/config.py +5060 -0
- hermes_cli/copilot_auth.py +392 -0
- hermes_cli/cron.py +307 -0
- hermes_cli/curator.py +589 -0
- hermes_cli/curses_ui.py +472 -0
- hermes_cli/debug.py +746 -0
- hermes_cli/default_soul.py +11 -0
- hermes_cli/dingtalk_auth.py +293 -0
- hermes_cli/doctor.py +1530 -0
- hermes_cli/dump.py +325 -0
- hermes_cli/env_loader.py +175 -0
- hermes_cli/fallback_cmd.py +361 -0
- hermes_cli/goals.py +535 -0
- hermes_cli/hooks.py +385 -0
- hermes_cli/kanban.py +2073 -0
- hermes_cli/kanban_db.py +4386 -0
- hermes_cli/kanban_diagnostics.py +649 -0
- hermes_cli/logs.py +390 -0
- hermes_cli/main.py +10932 -0
- hermes_cli/mcp_config.py +781 -0
- hermes_cli/memory_setup.py +457 -0
- hermes_cli/model_catalog.py +329 -0
- hermes_cli/model_normalize.py +473 -0
- hermes_cli/model_switch.py +1767 -0
- hermes_cli/models.py +3569 -0
- hermes_cli/nous_subscription.py +799 -0
- hermes_cli/oneshot.py +331 -0
- hermes_cli/pairing.py +115 -0
- hermes_cli/platforms.py +83 -0
- hermes_cli/plugins.py +1366 -0
- hermes_cli/plugins_cmd.py +1544 -0
- hermes_cli/profiles.py +1293 -0
- hermes_cli/providers.py +701 -0
- hermes_cli/pty_bridge.py +234 -0
- hermes_cli/relaunch.py +149 -0
- hermes_cli/runtime_provider.py +1344 -0
- hermes_cli/setup.py +3449 -0
- hermes_cli/skills_config.py +177 -0
- hermes_cli/skills_hub.py +1594 -0
- hermes_cli/skin_engine.py +892 -0
- hermes_cli/slack_cli.py +153 -0
- hermes_cli/status.py +551 -0
- hermes_cli/timeouts.py +82 -0
- hermes_cli/tips.py +487 -0
- hermes_cli/tools_config.py +2556 -0
- hermes_cli/uninstall.py +481 -0
- hermes_cli/vercel_auth.py +70 -0
- hermes_cli/voice.py +846 -0
- hermes_cli/web_server.py +4234 -0
- hermes_cli/webhook.py +274 -0
- hermes_constants.py +345 -0
- hermes_logging.py +389 -0
- hermes_state.py +2669 -0
- hermes_time.py +104 -0
- model_tools.py +847 -0
- orchestrator/__init__.py +67 -0
- orchestrator/bootstrap_wildcard.py +285 -0
- orchestrator/cloud_init.py +306 -0
- orchestrator/cloudflare.py +619 -0
- orchestrator/pool_manager.py +1005 -0
- orchestrator/provisioner.py +410 -0
- orchestrator/vm_destroyer.py +37 -0
- orchestrator/vm_lifecycle.py +671 -0
- plugins/__init__.py +1 -0
- plugins/atlas-architecture-review/__init__.py +23 -0
- plugins/atlas-atomic-decompose/__init__.py +23 -0
- plugins/atlas-bushido-pre-claim/__init__.py +27 -0
- plugins/atlas-dispatch-or-die/__init__.py +27 -0
- plugins/atlas-empirical-verify/__init__.py +36 -0
- plugins/atlas-hook-block-retry/__init__.py +34 -0
- plugins/atlas-idle-work-queue/__init__.py +22 -0
- plugins/context_engine/__init__.py +219 -0
- plugins/disk-cleanup/__init__.py +316 -0
- plugins/disk-cleanup/disk_cleanup.py +496 -0
- plugins/example-dashboard/dashboard/plugin_api.py +14 -0
- plugins/google_meet/__init__.py +103 -0
- plugins/google_meet/audio_bridge.py +244 -0
- plugins/google_meet/cli.py +478 -0
- plugins/google_meet/meet_bot.py +852 -0
- plugins/google_meet/node/__init__.py +54 -0
- plugins/google_meet/node/cli.py +125 -0
- plugins/google_meet/node/client.py +107 -0
- plugins/google_meet/node/protocol.py +124 -0
- plugins/google_meet/node/registry.py +112 -0
- plugins/google_meet/node/server.py +200 -0
- plugins/google_meet/process_manager.py +326 -0
- plugins/google_meet/realtime/__init__.py +10 -0
- plugins/google_meet/realtime/openai_client.py +332 -0
- plugins/google_meet/tools.py +348 -0
- plugins/hermes-achievements/dashboard/plugin_api.py +1061 -0
- plugins/hermes-achievements/tests/test_achievement_engine.py +156 -0
- plugins/image_gen/openai/__init__.py +303 -0
- plugins/image_gen/openai-codex/__init__.py +378 -0
- plugins/image_gen/xai/__init__.py +314 -0
- plugins/kanban/dashboard/plugin_api.py +1536 -0
- plugins/memory/__init__.py +407 -0
- plugins/memory/byterover/__init__.py +383 -0
- plugins/memory/hindsight/__init__.py +1747 -0
- plugins/memory/holographic/__init__.py +408 -0
- plugins/memory/holographic/holographic.py +203 -0
- plugins/memory/holographic/retrieval.py +593 -0
- plugins/memory/holographic/store.py +574 -0
- plugins/memory/honcho/__init__.py +1328 -0
- plugins/memory/honcho/cli.py +1451 -0
- plugins/memory/honcho/client.py +766 -0
- plugins/memory/honcho/session.py +1255 -0
- plugins/memory/mem0/__init__.py +373 -0
- plugins/memory/openviking/__init__.py +937 -0
- plugins/memory/retaindb/__init__.py +766 -0
- plugins/memory/supermemory/__init__.py +791 -0
- plugins/model-providers/ai-gateway/__init__.py +43 -0
- plugins/model-providers/alibaba/__init__.py +13 -0
- plugins/model-providers/alibaba-coding-plan/__init__.py +21 -0
- plugins/model-providers/anthropic/__init__.py +52 -0
- plugins/model-providers/arcee/__init__.py +13 -0
- plugins/model-providers/azure-foundry/__init__.py +21 -0
- plugins/model-providers/bedrock/__init__.py +29 -0
- plugins/model-providers/copilot/__init__.py +58 -0
- plugins/model-providers/copilot-acp/__init__.py +34 -0
- plugins/model-providers/custom/__init__.py +68 -0
- plugins/model-providers/deepseek/__init__.py +20 -0
- plugins/model-providers/gemini/__init__.py +72 -0
- plugins/model-providers/gmi/__init__.py +26 -0
- plugins/model-providers/huggingface/__init__.py +20 -0
- plugins/model-providers/kilocode/__init__.py +14 -0
- plugins/model-providers/kimi-coding/__init__.py +71 -0
- plugins/model-providers/minimax/__init__.py +45 -0
- plugins/model-providers/nous/__init__.py +53 -0
- plugins/model-providers/nvidia/__init__.py +21 -0
- plugins/model-providers/ollama-cloud/__init__.py +14 -0
- plugins/model-providers/openai-codex/__init__.py +15 -0
- plugins/model-providers/opencode-zen/__init__.py +30 -0
- plugins/model-providers/openrouter/__init__.py +86 -0
- plugins/model-providers/qwen-oauth/__init__.py +82 -0
- plugins/model-providers/stepfun/__init__.py +14 -0
- plugins/model-providers/xai/__init__.py +15 -0
- plugins/model-providers/xiaomi/__init__.py +13 -0
- plugins/model-providers/zai/__init__.py +21 -0
- plugins/observability/langfuse/__init__.py +874 -0
- plugins/platforms/google_chat/__init__.py +3 -0
- plugins/platforms/google_chat/adapter.py +3085 -0
- plugins/platforms/google_chat/oauth.py +638 -0
- plugins/platforms/irc/__init__.py +3 -0
- plugins/platforms/irc/adapter.py +745 -0
- plugins/platforms/teams/__init__.py +3 -0
- plugins/platforms/teams/adapter.py +764 -0
- plugins/spotify/__init__.py +66 -0
- plugins/spotify/client.py +435 -0
- plugins/spotify/tools.py +454 -0
- providers/__init__.py +191 -0
- providers/base.py +165 -0
- rl_cli.py +446 -0
- run_agent.py +14648 -0
- satellite/__init__.py +40 -0
- satellite/client.py +284 -0
- server/__init__.py +10 -0
- server/app.py +148 -0
- tenants/__init__.py +33 -0
- tenants/auth.py +394 -0
- tenants/store.py +1354 -0
- tools/__init__.py +25 -0
- tools/ansi_strip.py +44 -0
- tools/approval.py +1258 -0
- tools/binary_extensions.py +42 -0
- tools/browser_camofox.py +603 -0
- tools/browser_camofox_state.py +47 -0
- tools/browser_cdp_tool.py +563 -0
- tools/browser_dialog_tool.py +148 -0
- tools/browser_providers/__init__.py +10 -0
- tools/browser_providers/base.py +59 -0
- tools/browser_providers/browser_use.py +215 -0
- tools/browser_providers/browserbase.py +217 -0
- tools/browser_providers/firecrawl.py +107 -0
- tools/browser_supervisor.py +1366 -0
- tools/browser_tool.py +3511 -0
- tools/budget_config.py +52 -0
- tools/checkpoint_manager.py +1638 -0
- tools/clarify_tool.py +141 -0
- tools/code_execution_tool.py +1621 -0
- tools/credential_files.py +436 -0
- tools/cronjob_tools.py +662 -0
- tools/debug_helpers.py +105 -0
- tools/delegate_tool.py +2597 -0
- tools/discord_tool.py +959 -0
- tools/env_passthrough.py +145 -0
- tools/environments/__init__.py +14 -0
- tools/environments/base.py +806 -0
- tools/environments/daytona.py +259 -0
- tools/environments/docker.py +645 -0
- tools/environments/file_sync.py +399 -0
- tools/environments/local.py +531 -0
- tools/environments/managed_modal.py +282 -0
- tools/environments/modal.py +460 -0
- tools/environments/modal_utils.py +199 -0
- tools/environments/singularity.py +262 -0
- tools/environments/ssh.py +295 -0
- tools/environments/vercel_sandbox.py +638 -0
- tools/feishu_doc_tool.py +131 -0
- tools/feishu_drive_tool.py +429 -0
- tools/file_operations.py +1561 -0
- tools/file_state.py +332 -0
- tools/file_tools.py +1143 -0
- tools/fuzzy_match.py +704 -0
- tools/homeassistant_tool.py +513 -0
- tools/image_generation_tool.py +1023 -0
- tools/interrupt.py +98 -0
- tools/kanban_tools.py +871 -0
- tools/managed_tool_gateway.py +167 -0
- tools/mcp_oauth.py +632 -0
- tools/mcp_oauth_manager.py +607 -0
- tools/mcp_tool.py +3403 -0
- tools/memory_tool.py +586 -0
- tools/mixture_of_agents_tool.py +541 -0
- tools/neutts_synth.py +104 -0
- tools/openrouter_client.py +33 -0
- tools/osv_check.py +155 -0
- tools/patch_parser.py +592 -0
- tools/path_security.py +43 -0
- tools/process_registry.py +1434 -0
- tools/registry.py +537 -0
- tools/rl_training_tool.py +1396 -0
- tools/schema_sanitizer.py +370 -0
- tools/send_message_tool.py +1788 -0
- tools/session_search_tool.py +605 -0
- tools/skill_manager_tool.py +931 -0
- tools/skill_provenance.py +78 -0
- tools/skill_usage.py +609 -0
- tools/skills_guard.py +932 -0
- tools/skills_hub.py +3229 -0
- tools/skills_sync.py +431 -0
- tools/skills_tool.py +1533 -0
- tools/slash_confirm.py +162 -0
- tools/terminal_tool.py +2342 -0
- tools/tirith_security.py +691 -0
- tools/todo_tool.py +277 -0
- tools/tool_backend_helpers.py +144 -0
- tools/tool_output_limits.py +92 -0
- tools/tool_result_storage.py +226 -0
- tools/transcription_tools.py +911 -0
- tools/tts_tool.py +2185 -0
- tools/url_safety.py +327 -0
- tools/vision_tools.py +1168 -0
- tools/voice_mode.py +1017 -0
- tools/web_providers/__init__.py +6 -0
- tools/web_providers/base.py +89 -0
- tools/web_providers/searxng.py +131 -0
- tools/web_tools.py +2223 -0
- tools/website_policy.py +282 -0
- tools/xai_http.py +12 -0
- tools/yuanbao_tools.py +736 -0
- toolset_distributions.py +364 -0
- toolsets.py +839 -0
- trajectory_compressor.py +1508 -0
- utils.py +297 -0
- vm_runtime/__init__.py +24 -0
- vm_runtime/agent_client.py +589 -0
- vm_runtime/cortex_middleware.py +1400 -0
- vm_runtime/memory_manager.py +427 -0
- vm_runtime/server.py +867 -0
_atlas_version.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Single source of truth for the Atlas product line version.
|
|
2
|
+
|
|
3
|
+
Imported by every sub-package that surfaces a version field (brain,
|
|
4
|
+
vm_runtime, atlas_cli, atlas_agent_internal, orchestrator). Bump
|
|
5
|
+
:data:`ATLAS_VERSION` here
|
|
6
|
+
and every ``/healthz``, FastAPI ``version=...``, and CLI ``--version``
|
|
7
|
+
flows through automatically.
|
|
8
|
+
|
|
9
|
+
The pyproject.toml ``version`` field tracks the major.minor.patch part
|
|
10
|
+
only (PEP 440 strict, no local-pre suffixes). The ``-sprintN`` suffix
|
|
11
|
+
lives in this module because it carries pre-release semantics that
|
|
12
|
+
PyPI does not need to see, but runtime surfaces and operators do.
|
|
13
|
+
|
|
14
|
+
Ghost-strike audit 2026-05-13 crack M1 — replaces the prior fan of
|
|
15
|
+
divergent string literals (``BRAIN_VERSION = "0.4.0-phase4a"``,
|
|
16
|
+
``pyproject.version = "0.14.0"``) that contradicted the canonical
|
|
17
|
+
``v0.5.0-sprint2-validated`` project-state.
|
|
18
|
+
|
|
19
|
+
This file lives at ``atlas-core/atlas/_atlas_version.py`` so that the
|
|
20
|
+
existing bare-name import path (``import _atlas_version``) works under
|
|
21
|
+
the current pytest rootdir convention (``atlas-core/atlas/`` on
|
|
22
|
+
``sys.path``). The leading underscore signals private/internal — the
|
|
23
|
+
public re-export is via each sub-package's own version constant.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
__all__ = ["ATLAS_VERSION", "__version__"]
|
|
28
|
+
|
|
29
|
+
# Bump this string to bump the whole Atlas product line.
|
|
30
|
+
ATLAS_VERSION = "0.5.0-sprint2"
|
|
31
|
+
__version__ = ATLAS_VERSION
|
agent/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Agent internals -- extracted modules from run_agent.py.
|
|
2
|
+
|
|
3
|
+
These modules contain pure utility functions and self-contained classes
|
|
4
|
+
that were previously embedded in the 3,600-line run_agent.py. Extracting
|
|
5
|
+
them makes run_agent.py focused on the AIAgent orchestrator class.
|
|
6
|
+
"""
|
agent/account_usage.py
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
from agent.anthropic_adapter import _is_oauth_token, resolve_anthropic_token
|
|
10
|
+
from hermes_cli.auth import _read_codex_tokens, resolve_codex_runtime_credentials
|
|
11
|
+
from hermes_cli.runtime_provider import resolve_runtime_provider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _utc_now() -> datetime:
|
|
15
|
+
return datetime.now(timezone.utc)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class AccountUsageWindow:
|
|
20
|
+
label: str
|
|
21
|
+
used_percent: Optional[float] = None
|
|
22
|
+
reset_at: Optional[datetime] = None
|
|
23
|
+
detail: Optional[str] = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class AccountUsageSnapshot:
|
|
28
|
+
provider: str
|
|
29
|
+
source: str
|
|
30
|
+
fetched_at: datetime
|
|
31
|
+
title: str = "Account limits"
|
|
32
|
+
plan: Optional[str] = None
|
|
33
|
+
windows: tuple[AccountUsageWindow, ...] = ()
|
|
34
|
+
details: tuple[str, ...] = ()
|
|
35
|
+
unavailable_reason: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def available(self) -> bool:
|
|
39
|
+
return bool(self.windows or self.details) and not self.unavailable_reason
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _title_case_slug(value: Optional[str]) -> Optional[str]:
|
|
43
|
+
cleaned = str(value or "").strip()
|
|
44
|
+
if not cleaned:
|
|
45
|
+
return None
|
|
46
|
+
return cleaned.replace("_", " ").replace("-", " ").title()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _parse_dt(value: Any) -> Optional[datetime]:
|
|
50
|
+
if value in (None, ""):
|
|
51
|
+
return None
|
|
52
|
+
if isinstance(value, (int, float)):
|
|
53
|
+
return datetime.fromtimestamp(float(value), tz=timezone.utc)
|
|
54
|
+
if isinstance(value, str):
|
|
55
|
+
text = value.strip()
|
|
56
|
+
if not text:
|
|
57
|
+
return None
|
|
58
|
+
if text.endswith("Z"):
|
|
59
|
+
text = text[:-1] + "+00:00"
|
|
60
|
+
try:
|
|
61
|
+
dt = datetime.fromisoformat(text)
|
|
62
|
+
return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc)
|
|
63
|
+
except ValueError:
|
|
64
|
+
return None
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _format_reset(dt: Optional[datetime]) -> str:
|
|
69
|
+
if not dt:
|
|
70
|
+
return "unknown"
|
|
71
|
+
local_dt = dt.astimezone()
|
|
72
|
+
delta = dt - _utc_now()
|
|
73
|
+
total_seconds = int(delta.total_seconds())
|
|
74
|
+
if total_seconds <= 0:
|
|
75
|
+
return f"now ({local_dt.strftime('%Y-%m-%d %H:%M %Z')})"
|
|
76
|
+
hours, rem = divmod(total_seconds, 3600)
|
|
77
|
+
minutes = rem // 60
|
|
78
|
+
if hours >= 24:
|
|
79
|
+
days, hours = divmod(hours, 24)
|
|
80
|
+
rel = f"in {days}d {hours}h"
|
|
81
|
+
elif hours > 0:
|
|
82
|
+
rel = f"in {hours}h {minutes}m"
|
|
83
|
+
else:
|
|
84
|
+
rel = f"in {minutes}m"
|
|
85
|
+
return f"{rel} ({local_dt.strftime('%Y-%m-%d %H:%M %Z')})"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def render_account_usage_lines(snapshot: Optional[AccountUsageSnapshot], *, markdown: bool = False) -> list[str]:
|
|
89
|
+
if not snapshot:
|
|
90
|
+
return []
|
|
91
|
+
header = f"📈 {'**' if markdown else ''}{snapshot.title}{'**' if markdown else ''}"
|
|
92
|
+
lines = [header]
|
|
93
|
+
if snapshot.plan:
|
|
94
|
+
lines.append(f"Provider: {snapshot.provider} ({snapshot.plan})")
|
|
95
|
+
else:
|
|
96
|
+
lines.append(f"Provider: {snapshot.provider}")
|
|
97
|
+
for window in snapshot.windows:
|
|
98
|
+
if window.used_percent is None:
|
|
99
|
+
base = f"{window.label}: unavailable"
|
|
100
|
+
else:
|
|
101
|
+
remaining = max(0, round(100 - float(window.used_percent)))
|
|
102
|
+
used = max(0, round(float(window.used_percent)))
|
|
103
|
+
base = f"{window.label}: {remaining}% remaining ({used}% used)"
|
|
104
|
+
if window.reset_at:
|
|
105
|
+
base += f" • resets {_format_reset(window.reset_at)}"
|
|
106
|
+
elif window.detail:
|
|
107
|
+
base += f" • {window.detail}"
|
|
108
|
+
lines.append(base)
|
|
109
|
+
for detail in snapshot.details:
|
|
110
|
+
lines.append(detail)
|
|
111
|
+
if snapshot.unavailable_reason:
|
|
112
|
+
lines.append(f"Unavailable: {snapshot.unavailable_reason}")
|
|
113
|
+
return lines
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _resolve_codex_usage_url(base_url: str) -> str:
|
|
117
|
+
normalized = (base_url or "").strip().rstrip("/")
|
|
118
|
+
if not normalized:
|
|
119
|
+
normalized = "https://chatgpt.com/backend-api/codex"
|
|
120
|
+
if normalized.endswith("/codex"):
|
|
121
|
+
normalized = normalized[: -len("/codex")]
|
|
122
|
+
if "/backend-api" in normalized:
|
|
123
|
+
return normalized + "/wham/usage"
|
|
124
|
+
return normalized + "/api/codex/usage"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _fetch_codex_account_usage() -> Optional[AccountUsageSnapshot]:
|
|
128
|
+
creds = resolve_codex_runtime_credentials(refresh_if_expiring=True)
|
|
129
|
+
token_data = _read_codex_tokens()
|
|
130
|
+
tokens = token_data.get("tokens") or {}
|
|
131
|
+
account_id = str(tokens.get("account_id", "") or "").strip() or None
|
|
132
|
+
headers = {
|
|
133
|
+
"Authorization": f"Bearer {creds['api_key']}",
|
|
134
|
+
"Accept": "application/json",
|
|
135
|
+
"User-Agent": "codex-cli",
|
|
136
|
+
}
|
|
137
|
+
if account_id:
|
|
138
|
+
headers["ChatGPT-Account-Id"] = account_id
|
|
139
|
+
with httpx.Client(timeout=15.0) as client:
|
|
140
|
+
response = client.get(_resolve_codex_usage_url(creds.get("base_url", "")), headers=headers)
|
|
141
|
+
response.raise_for_status()
|
|
142
|
+
payload = response.json() or {}
|
|
143
|
+
rate_limit = payload.get("rate_limit") or {}
|
|
144
|
+
windows: list[AccountUsageWindow] = []
|
|
145
|
+
for key, label in (("primary_window", "Session"), ("secondary_window", "Weekly")):
|
|
146
|
+
window = rate_limit.get(key) or {}
|
|
147
|
+
used = window.get("used_percent")
|
|
148
|
+
if used is None:
|
|
149
|
+
continue
|
|
150
|
+
windows.append(
|
|
151
|
+
AccountUsageWindow(
|
|
152
|
+
label=label,
|
|
153
|
+
used_percent=float(used),
|
|
154
|
+
reset_at=_parse_dt(window.get("reset_at")),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
details: list[str] = []
|
|
158
|
+
credits = payload.get("credits") or {}
|
|
159
|
+
if credits.get("has_credits"):
|
|
160
|
+
balance = credits.get("balance")
|
|
161
|
+
if isinstance(balance, (int, float)):
|
|
162
|
+
details.append(f"Credits balance: ${float(balance):.2f}")
|
|
163
|
+
elif credits.get("unlimited"):
|
|
164
|
+
details.append("Credits balance: unlimited")
|
|
165
|
+
return AccountUsageSnapshot(
|
|
166
|
+
provider="openai-codex",
|
|
167
|
+
source="usage_api",
|
|
168
|
+
fetched_at=_utc_now(),
|
|
169
|
+
plan=_title_case_slug(payload.get("plan_type")),
|
|
170
|
+
windows=tuple(windows),
|
|
171
|
+
details=tuple(details),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _fetch_anthropic_account_usage() -> Optional[AccountUsageSnapshot]:
|
|
176
|
+
token = (resolve_anthropic_token() or "").strip()
|
|
177
|
+
if not token:
|
|
178
|
+
return None
|
|
179
|
+
if not _is_oauth_token(token):
|
|
180
|
+
return AccountUsageSnapshot(
|
|
181
|
+
provider="anthropic",
|
|
182
|
+
source="oauth_usage_api",
|
|
183
|
+
fetched_at=_utc_now(),
|
|
184
|
+
unavailable_reason="Anthropic account limits are only available for OAuth-backed Claude accounts.",
|
|
185
|
+
)
|
|
186
|
+
headers = {
|
|
187
|
+
"Authorization": f"Bearer {token}",
|
|
188
|
+
"Accept": "application/json",
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
191
|
+
"User-Agent": "claude-code/2.1.0",
|
|
192
|
+
}
|
|
193
|
+
with httpx.Client(timeout=15.0) as client:
|
|
194
|
+
response = client.get("https://api.anthropic.com/api/oauth/usage", headers=headers)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
payload = response.json() or {}
|
|
197
|
+
windows: list[AccountUsageWindow] = []
|
|
198
|
+
mapping = (
|
|
199
|
+
("five_hour", "Current session"),
|
|
200
|
+
("seven_day", "Current week"),
|
|
201
|
+
("seven_day_opus", "Opus week"),
|
|
202
|
+
("seven_day_sonnet", "Sonnet week"),
|
|
203
|
+
)
|
|
204
|
+
for key, label in mapping:
|
|
205
|
+
window = payload.get(key) or {}
|
|
206
|
+
util = window.get("utilization")
|
|
207
|
+
if util is None:
|
|
208
|
+
continue
|
|
209
|
+
used = float(util) * 100 if float(util) <= 1 else float(util)
|
|
210
|
+
windows.append(
|
|
211
|
+
AccountUsageWindow(
|
|
212
|
+
label=label,
|
|
213
|
+
used_percent=used,
|
|
214
|
+
reset_at=_parse_dt(window.get("resets_at")),
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
details: list[str] = []
|
|
218
|
+
extra = payload.get("extra_usage") or {}
|
|
219
|
+
if extra.get("is_enabled"):
|
|
220
|
+
used_credits = extra.get("used_credits")
|
|
221
|
+
monthly_limit = extra.get("monthly_limit")
|
|
222
|
+
currency = extra.get("currency") or "USD"
|
|
223
|
+
if isinstance(used_credits, (int, float)) and isinstance(monthly_limit, (int, float)):
|
|
224
|
+
details.append(
|
|
225
|
+
f"Extra usage: {used_credits:.2f} / {monthly_limit:.2f} {currency}"
|
|
226
|
+
)
|
|
227
|
+
return AccountUsageSnapshot(
|
|
228
|
+
provider="anthropic",
|
|
229
|
+
source="oauth_usage_api",
|
|
230
|
+
fetched_at=_utc_now(),
|
|
231
|
+
windows=tuple(windows),
|
|
232
|
+
details=tuple(details),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _fetch_openrouter_account_usage(base_url: Optional[str], api_key: Optional[str]) -> Optional[AccountUsageSnapshot]:
|
|
237
|
+
runtime = resolve_runtime_provider(
|
|
238
|
+
requested="openrouter",
|
|
239
|
+
explicit_base_url=base_url,
|
|
240
|
+
explicit_api_key=api_key,
|
|
241
|
+
)
|
|
242
|
+
token = str(runtime.get("api_key", "") or "").strip()
|
|
243
|
+
if not token:
|
|
244
|
+
return None
|
|
245
|
+
normalized = str(runtime.get("base_url", "") or "").rstrip("/")
|
|
246
|
+
credits_url = f"{normalized}/credits"
|
|
247
|
+
key_url = f"{normalized}/key"
|
|
248
|
+
headers = {
|
|
249
|
+
"Authorization": f"Bearer {token}",
|
|
250
|
+
"Accept": "application/json",
|
|
251
|
+
}
|
|
252
|
+
with httpx.Client(timeout=10.0) as client:
|
|
253
|
+
credits_resp = client.get(credits_url, headers=headers)
|
|
254
|
+
credits_resp.raise_for_status()
|
|
255
|
+
credits = (credits_resp.json() or {}).get("data") or {}
|
|
256
|
+
try:
|
|
257
|
+
key_resp = client.get(key_url, headers=headers)
|
|
258
|
+
key_resp.raise_for_status()
|
|
259
|
+
key_data = (key_resp.json() or {}).get("data") or {}
|
|
260
|
+
except Exception:
|
|
261
|
+
key_data = {}
|
|
262
|
+
total_credits = float(credits.get("total_credits") or 0.0)
|
|
263
|
+
total_usage = float(credits.get("total_usage") or 0.0)
|
|
264
|
+
details = [f"Credits balance: ${max(0.0, total_credits - total_usage):.2f}"]
|
|
265
|
+
windows: list[AccountUsageWindow] = []
|
|
266
|
+
limit = key_data.get("limit")
|
|
267
|
+
limit_remaining = key_data.get("limit_remaining")
|
|
268
|
+
limit_reset = str(key_data.get("limit_reset") or "").strip()
|
|
269
|
+
usage = key_data.get("usage")
|
|
270
|
+
if (
|
|
271
|
+
isinstance(limit, (int, float))
|
|
272
|
+
and float(limit) > 0
|
|
273
|
+
and isinstance(limit_remaining, (int, float))
|
|
274
|
+
and 0 <= float(limit_remaining) <= float(limit)
|
|
275
|
+
):
|
|
276
|
+
limit_value = float(limit)
|
|
277
|
+
remaining_value = float(limit_remaining)
|
|
278
|
+
used_percent = ((limit_value - remaining_value) / limit_value) * 100
|
|
279
|
+
detail_parts = [f"${remaining_value:.2f} of ${limit_value:.2f} remaining"]
|
|
280
|
+
if limit_reset:
|
|
281
|
+
detail_parts.append(f"resets {limit_reset}")
|
|
282
|
+
windows.append(
|
|
283
|
+
AccountUsageWindow(
|
|
284
|
+
label="API key quota",
|
|
285
|
+
used_percent=used_percent,
|
|
286
|
+
detail=" • ".join(detail_parts),
|
|
287
|
+
)
|
|
288
|
+
)
|
|
289
|
+
if isinstance(usage, (int, float)):
|
|
290
|
+
usage_parts = [f"API key usage: ${float(usage):.2f} total"]
|
|
291
|
+
for value, label in (
|
|
292
|
+
(key_data.get("usage_daily"), "today"),
|
|
293
|
+
(key_data.get("usage_weekly"), "this week"),
|
|
294
|
+
(key_data.get("usage_monthly"), "this month"),
|
|
295
|
+
):
|
|
296
|
+
if isinstance(value, (int, float)) and float(value) > 0:
|
|
297
|
+
usage_parts.append(f"${float(value):.2f} {label}")
|
|
298
|
+
details.append(" • ".join(usage_parts))
|
|
299
|
+
return AccountUsageSnapshot(
|
|
300
|
+
provider="openrouter",
|
|
301
|
+
source="credits_api",
|
|
302
|
+
fetched_at=_utc_now(),
|
|
303
|
+
windows=tuple(windows),
|
|
304
|
+
details=tuple(details),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def fetch_account_usage(
|
|
309
|
+
provider: Optional[str],
|
|
310
|
+
*,
|
|
311
|
+
base_url: Optional[str] = None,
|
|
312
|
+
api_key: Optional[str] = None,
|
|
313
|
+
) -> Optional[AccountUsageSnapshot]:
|
|
314
|
+
normalized = str(provider or "").strip().lower()
|
|
315
|
+
if normalized in {"", "auto", "custom"}:
|
|
316
|
+
return None
|
|
317
|
+
try:
|
|
318
|
+
if normalized == "openai-codex":
|
|
319
|
+
return _fetch_codex_account_usage()
|
|
320
|
+
if normalized == "anthropic":
|
|
321
|
+
return _fetch_anthropic_account_usage()
|
|
322
|
+
if normalized == "openrouter":
|
|
323
|
+
return _fetch_openrouter_account_usage(base_url, api_key)
|
|
324
|
+
except Exception:
|
|
325
|
+
return None
|
|
326
|
+
return None
|