aria-code 0.52.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.
- aria_code/__init__.py +42 -0
- aria_code/_version.py +20 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +34 -0
- aria_code/agents/base.py +318 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +303 -0
- aria_code/agents/financial/fundamental.py +191 -0
- aria_code/agents/financial/macro.py +99 -0
- aria_code/agents/financial/news.py +207 -0
- aria_code/agents/financial/risk.py +165 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +258 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +363 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +158 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +492 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +15 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +95 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/bootstrap.py +145 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +16 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +369 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +239 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
- aria_code/apps/cli/commands/broker_cmds.py +1234 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +87 -0
- aria_code/apps/cli/commands/core_cmds.py +2520 -0
- aria_code/apps/cli/commands/data_cmds.py +457 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +434 -0
- aria_code/apps/cli/commands/market_cmds.py +1381 -0
- aria_code/apps/cli/commands/market_context.py +425 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2153 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
- aria_code/apps/cli/commands/report.py +437 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +669 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
- aria_code/apps/cli/commands/workflow_cmds.py +480 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +78 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/deterministic.py +191 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +196 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
- aria_code/apps/cli/handlers/market_handlers.py +2513 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/helpers.py +302 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +297 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +276 -0
- aria_code/apps/cli/intent_signals.py +344 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +536 -0
- aria_code/apps/cli/main.py +29 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/model_catalog.py +620 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +59 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +53 -0
- aria_code/apps/cli/prompts/select.py +142 -0
- aria_code/apps/cli/prompts/system_prompts.py +552 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +521 -0
- aria_code/apps/cli/providers/chat_routing.py +128 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
- aria_code/apps/cli/providers/runtime_bridge.py +353 -0
- aria_code/apps/cli/providers/vertexai_stream.py +316 -0
- aria_code/apps/cli/runtime_consumer.py +657 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_store.py +112 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +823 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +106 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +180 -0
- aria_code/apps/cli/tools/market_tools.py +620 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +726 -0
- aria_code/apps/cli/tools/write_tools.py +845 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +153 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1597 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +6743 -0
- aria_code/aria_daemon.py +1398 -0
- aria_code/aria_feishu_bot.py +1423 -0
- aria_code/aria_mcp_server.py +7 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_relay_server.py +490 -0
- aria_code/aria_telegram_bot.py +202 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +182 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1976 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +181 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/demo_player.py +207 -0
- aria_code/doctor.py +813 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/backtest_engine.py +354 -0
- aria_code/domain/backtest_report.py +472 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/harness.py +598 -0
- aria_code/evals/runner.py +238 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +386 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +639 -0
- aria_code/memory_manager.py +246 -0
- aria_code/model_capability.py +486 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +23 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +52 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +201 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +111 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1550 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +396 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +79 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +268 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +57 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +157 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +8 -0
- aria_code/packages/aria_tools/financial/_paths.py +130 -0
- aria_code/packages/aria_tools/financial/compliance.py +31 -0
- aria_code/packages/aria_tools/financial/factors.py +33 -0
- aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
- aria_code/packages/aria_tools/financial/strategy.py +31 -0
- aria_code/packages/aria_tools/registry.py +128 -0
- aria_code/packages/quant_engine/__init__.py +34 -0
- aria_code/packages/quant_engine/_namespace.py +62 -0
- aria_code/packages/quant_engine/backtest/__init__.py +14 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +96 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +80 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/ml_model.py +377 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/logistics/__init__.py +169 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +129 -0
- aria_code/providers/llm/openai_compat.py +313 -0
- aria_code/providers/llm/registry.py +444 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +589 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/runtime/__init__.py +169 -0
- aria_code/runtime/acceptance.py +481 -0
- aria_code/runtime/agent_loop.py +1520 -0
- aria_code/runtime/approval.py +57 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +432 -0
- aria_code/runtime/crash_report.py +174 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +150 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/repo_map.py +700 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +450 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/subagent.py +547 -0
- aria_code/runtime/task_ledger.py +86 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/tool_executor.py +217 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +23 -0
- aria_code/safety/permissions.py +306 -0
- aria_code/safety/service.py +102 -0
- aria_code/setup_wizard.py +673 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +504 -0
- aria_code/tools/data_analysis_tools.py +809 -0
- aria_code/tools/enterprise_finance_tools.py +156 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +885 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3417 -0
- aria_code/tools/logistics_tools.py +135 -0
- aria_code/tools/macro_tools.py +369 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +659 -0
- aria_code/tools/spreadsheet_tools.py +388 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +508 -0
- aria_code/ui/completer.py +501 -0
- aria_code/ui/console.py +281 -0
- aria_code/ui/image_render.py +243 -0
- aria_code/ui/input_box.py +488 -0
- aria_code/ui/picker.py +201 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/finance.py +1705 -0
- aria_code/ui/render/market.py +225 -0
- aria_code/ui/render/output.py +775 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +354 -0
- aria_code/ui/robot.py +235 -0
- aria_code/ui/startup_dashboard.py +109 -0
- aria_code/utils/vscode_uri.py +37 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +271 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +193 -0
- aria_code/workspace/verify.py +113 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_context.py +233 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.52.0.dist-info/METADATA +237 -0
- aria_code-0.52.0.dist-info/RECORD +478 -0
- aria_code-0.52.0.dist-info/WHEEL +5 -0
- aria_code-0.52.0.dist-info/entry_points.txt +3 -0
- aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.52.0.dist-info/top_level.txt +1 -0
aria_code/ui/console.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Shared Rich console, availability flags, and ESC-key watcher.
|
|
2
|
+
|
|
3
|
+
Import this instead of repeating the try/except blocks everywhere:
|
|
4
|
+
|
|
5
|
+
from ui.console import console, HAS_RICH, HAS_PT, _SYNTAX_THEME
|
|
6
|
+
from ui.console import _EscWatcher, _esc_watcher
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import threading
|
|
16
|
+
import time
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
# ── Rich ───────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from rich.console import Console
|
|
23
|
+
from rich.markdown import Markdown
|
|
24
|
+
from rich.live import Live
|
|
25
|
+
from rich.text import Text
|
|
26
|
+
from rich.status import Status
|
|
27
|
+
from rich.syntax import Syntax
|
|
28
|
+
from rich.panel import Panel
|
|
29
|
+
from rich.rule import Rule
|
|
30
|
+
from rich import box as rich_box
|
|
31
|
+
from rich.theme import Theme
|
|
32
|
+
HAS_RICH = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
HAS_RICH = False
|
|
35
|
+
|
|
36
|
+
# ── prompt_toolkit ─────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from prompt_toolkit import PromptSession
|
|
40
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
41
|
+
from prompt_toolkit.formatted_text import HTML
|
|
42
|
+
from prompt_toolkit.history import FileHistory
|
|
43
|
+
from prompt_toolkit.styles import Style as PTStyle
|
|
44
|
+
HAS_PT = True
|
|
45
|
+
except ImportError:
|
|
46
|
+
HAS_PT = False
|
|
47
|
+
|
|
48
|
+
# ── Console singleton ──────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
_SYNTAX_THEME: str = "monokai"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _detect_terminal_theme() -> str:
|
|
54
|
+
"""Return a best-effort terminal theme: ``dark`` or ``light``."""
|
|
55
|
+
explicit = os.getenv("ARIA_RICH_THEME", os.getenv("ARIA_INPUT_THEME", "")).strip().lower()
|
|
56
|
+
if explicit in {"dark", "light"}:
|
|
57
|
+
return explicit
|
|
58
|
+
colorfgbg = os.getenv("COLORFGBG", "")
|
|
59
|
+
if colorfgbg:
|
|
60
|
+
try:
|
|
61
|
+
return "dark" if int(colorfgbg.split(";")[-1]) < 8 else "light"
|
|
62
|
+
except ValueError:
|
|
63
|
+
pass
|
|
64
|
+
# macOS appearance is not the Terminal profile appearance. Apple Terminal's
|
|
65
|
+
# default Basic profile is light even when the OS itself is in dark mode;
|
|
66
|
+
# using AppleInterfaceStyle here produced pale dark-theme headings on white.
|
|
67
|
+
if os.getenv("TERM_PROGRAM", "") == "Apple_Terminal":
|
|
68
|
+
return "light"
|
|
69
|
+
# os.uname() is Unix-only — doesn't exist on Windows at all. sys.platform
|
|
70
|
+
# is the portable equivalent and already covers the "Darwin" case here.
|
|
71
|
+
if sys.platform == "darwin":
|
|
72
|
+
try:
|
|
73
|
+
r = subprocess.run(
|
|
74
|
+
["defaults", "read", "-g", "AppleInterfaceStyle"],
|
|
75
|
+
capture_output=True, text=True, timeout=0.2, check=False,
|
|
76
|
+
)
|
|
77
|
+
return "dark" if (r.returncode == 0 and "dark" in r.stdout.lower()) else "light"
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
return "dark"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _build_rich_theme(theme: str) -> "Theme":
|
|
84
|
+
"""Build a Markdown palette with enough contrast for the terminal theme."""
|
|
85
|
+
if theme == "light":
|
|
86
|
+
return Theme({
|
|
87
|
+
"markdown.h1": "bold #24292f",
|
|
88
|
+
"markdown.h2": "bold #24292f",
|
|
89
|
+
"markdown.h3": "bold #8a5a00",
|
|
90
|
+
"markdown.h4": "bold #8a5a00",
|
|
91
|
+
"markdown.h5": "bold #8a5a00",
|
|
92
|
+
"markdown.h6": "bold #8a5a00",
|
|
93
|
+
"markdown.heading": "bold #24292f",
|
|
94
|
+
"markdown.code": "bold #8a5a00",
|
|
95
|
+
"markdown.code_inline": "bold #8a5a00",
|
|
96
|
+
"markdown.link": "underline #0969da",
|
|
97
|
+
"markdown.link_url": "underline #57606a",
|
|
98
|
+
"markdown.item.bullet": "bold #8a5a00",
|
|
99
|
+
"markdown.item.number": "bold #8a5a00",
|
|
100
|
+
"markdown.table.header": "bold #8a5a00",
|
|
101
|
+
"markdown.table.border": "#8c959f",
|
|
102
|
+
"markdown.hr": "#8c959f",
|
|
103
|
+
"markdown.strong": "bold #1f2328",
|
|
104
|
+
"markdown.em": "italic #57606a",
|
|
105
|
+
"markdown.block_quote": "#6e7781",
|
|
106
|
+
})
|
|
107
|
+
return Theme({
|
|
108
|
+
# Keep Markdown close to the terminal palette: neutral text, copper
|
|
109
|
+
# accents, no blue/purple headings, and no black inline-code blocks.
|
|
110
|
+
"markdown.h1": "bold #e8e0d4",
|
|
111
|
+
"markdown.h2": "bold #e8e0d4",
|
|
112
|
+
"markdown.h3": "bold #d6ba8e",
|
|
113
|
+
"markdown.h4": "bold #d6ba8e",
|
|
114
|
+
"markdown.h5": "bold #d6ba8e",
|
|
115
|
+
"markdown.h6": "bold #d6ba8e",
|
|
116
|
+
"markdown.heading": "bold #e8e0d4",
|
|
117
|
+
"markdown.code": "bold #c08050",
|
|
118
|
+
"markdown.code_inline": "bold #c08050",
|
|
119
|
+
"markdown.link": "underline #c08050",
|
|
120
|
+
"markdown.link_url": "underline #8f867a",
|
|
121
|
+
"markdown.item.bullet": "bold #d6ba8e",
|
|
122
|
+
"markdown.item.number": "bold #d6ba8e",
|
|
123
|
+
"markdown.table.header": "bold #d6ba8e",
|
|
124
|
+
"markdown.table.border": "#6f675d",
|
|
125
|
+
"markdown.hr": "#6f675d",
|
|
126
|
+
"markdown.strong": "bold #e8e0d4",
|
|
127
|
+
"markdown.em": "italic #c7beb2",
|
|
128
|
+
"markdown.block_quote": "#a8a096",
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if HAS_RICH:
|
|
133
|
+
ARIA_RICH_THEME_NAME = _detect_terminal_theme()
|
|
134
|
+
ARIA_RICH_THEME = _build_rich_theme(ARIA_RICH_THEME_NAME)
|
|
135
|
+
console = Console(highlight=False, theme=ARIA_RICH_THEME)
|
|
136
|
+
|
|
137
|
+
def make_markdown(markup: str) -> Markdown:
|
|
138
|
+
"""Create Markdown with Aria's low-saturation terminal theme."""
|
|
139
|
+
from ui.render.output import adapt_markdown_for_width
|
|
140
|
+
|
|
141
|
+
markup = adapt_markdown_for_width(markup, getattr(console, "width", 80))
|
|
142
|
+
return Markdown(
|
|
143
|
+
markup,
|
|
144
|
+
code_theme="bw",
|
|
145
|
+
inline_code_theme="bw",
|
|
146
|
+
)
|
|
147
|
+
else:
|
|
148
|
+
class _FallbackConsole:
|
|
149
|
+
def print(self, *a, **kw):
|
|
150
|
+
print(*[str(x) for x in a])
|
|
151
|
+
|
|
152
|
+
def input(self, prompt: str = "") -> str:
|
|
153
|
+
return input(prompt)
|
|
154
|
+
|
|
155
|
+
def status(self, msg: str):
|
|
156
|
+
class _Ctx:
|
|
157
|
+
def __enter__(self):
|
|
158
|
+
print(msg)
|
|
159
|
+
return self
|
|
160
|
+
def __exit__(self, *a):
|
|
161
|
+
pass
|
|
162
|
+
def update(self, msg: str):
|
|
163
|
+
print(msg)
|
|
164
|
+
return _Ctx()
|
|
165
|
+
|
|
166
|
+
console = _FallbackConsole()
|
|
167
|
+
|
|
168
|
+
def make_markdown(markup: str) -> str:
|
|
169
|
+
return markup
|
|
170
|
+
|
|
171
|
+
# ── termios / raw-mode availability ───────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
import termios
|
|
175
|
+
import tty
|
|
176
|
+
import select as _select
|
|
177
|
+
_HAS_TERMIOS = True
|
|
178
|
+
except ImportError:
|
|
179
|
+
_HAS_TERMIOS = False
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ── ESC-key watcher ───────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
class _EscWatcher:
|
|
185
|
+
"""Background thread that watches for ESC key press to cancel streaming."""
|
|
186
|
+
|
|
187
|
+
def __init__(self):
|
|
188
|
+
self._active = False
|
|
189
|
+
self._paused = False
|
|
190
|
+
self._thread: Optional[threading.Thread] = None
|
|
191
|
+
self._old_settings = None
|
|
192
|
+
self._cancel_event: Optional[asyncio.Event] = None
|
|
193
|
+
self._fd: Optional[int] = None
|
|
194
|
+
|
|
195
|
+
def start(self, cancel_event: asyncio.Event):
|
|
196
|
+
if not _HAS_TERMIOS or not sys.stdin.isatty():
|
|
197
|
+
return
|
|
198
|
+
self._cancel_event = cancel_event
|
|
199
|
+
self._fd = sys.stdin.fileno()
|
|
200
|
+
try:
|
|
201
|
+
self._old_settings = termios.tcgetattr(self._fd)
|
|
202
|
+
tty.setcbreak(self._fd)
|
|
203
|
+
except Exception:
|
|
204
|
+
self._old_settings = None
|
|
205
|
+
return
|
|
206
|
+
self._active = True
|
|
207
|
+
self._paused = False
|
|
208
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
209
|
+
self._thread.start()
|
|
210
|
+
|
|
211
|
+
def pause(self):
|
|
212
|
+
self._paused = True
|
|
213
|
+
if self._old_settings and self._fd is not None:
|
|
214
|
+
try:
|
|
215
|
+
termios.tcsetattr(self._fd, termios.TCSADRAIN, self._old_settings)
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
def resume(self):
|
|
220
|
+
if not self._active or not _HAS_TERMIOS or self._fd is None:
|
|
221
|
+
return
|
|
222
|
+
if self._cancel_event and self._cancel_event.is_set():
|
|
223
|
+
return
|
|
224
|
+
try:
|
|
225
|
+
termios.tcflush(self._fd, termios.TCIFLUSH)
|
|
226
|
+
self._old_settings = termios.tcgetattr(self._fd)
|
|
227
|
+
tty.setcbreak(self._fd)
|
|
228
|
+
except Exception:
|
|
229
|
+
return
|
|
230
|
+
self._paused = False
|
|
231
|
+
|
|
232
|
+
def stop(self):
|
|
233
|
+
self._active = False
|
|
234
|
+
self._paused = False
|
|
235
|
+
if self._old_settings and self._fd is not None:
|
|
236
|
+
try:
|
|
237
|
+
termios.tcsetattr(self._fd, termios.TCSADRAIN, self._old_settings)
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
self._old_settings = None
|
|
241
|
+
if self._thread:
|
|
242
|
+
self._thread.join(timeout=0.3)
|
|
243
|
+
self._thread = None
|
|
244
|
+
|
|
245
|
+
def _run(self):
|
|
246
|
+
fd = self._fd
|
|
247
|
+
try:
|
|
248
|
+
while self._active:
|
|
249
|
+
if self._paused:
|
|
250
|
+
time.sleep(0.1)
|
|
251
|
+
continue
|
|
252
|
+
try:
|
|
253
|
+
ready, _, _ = _select.select([fd], [], [], 0.15)
|
|
254
|
+
except (ValueError, OSError):
|
|
255
|
+
break
|
|
256
|
+
if not self._active or self._paused:
|
|
257
|
+
continue
|
|
258
|
+
if ready:
|
|
259
|
+
try:
|
|
260
|
+
ch = os.read(fd, 1)
|
|
261
|
+
except OSError:
|
|
262
|
+
break
|
|
263
|
+
if ch == b'\x1b':
|
|
264
|
+
try:
|
|
265
|
+
r2, _, _ = _select.select([fd], [], [], 0.05)
|
|
266
|
+
except (ValueError, OSError):
|
|
267
|
+
break
|
|
268
|
+
if r2:
|
|
269
|
+
try:
|
|
270
|
+
os.read(fd, 16)
|
|
271
|
+
except OSError:
|
|
272
|
+
pass
|
|
273
|
+
else:
|
|
274
|
+
if self._cancel_event:
|
|
275
|
+
self._cancel_event.set()
|
|
276
|
+
self._active = False
|
|
277
|
+
except Exception:
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
_esc_watcher = _EscWatcher()
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Terminal image rendering — show a real PNG in the terminal, with fallback.
|
|
2
|
+
|
|
3
|
+
Three layered techniques, best-first (exactly what chafa/viu/timg do):
|
|
4
|
+
|
|
5
|
+
1. iTerm2 inline images (OSC 1337) — iTerm2, WezTerm, ghostty
|
|
6
|
+
2. Kitty graphics protocol — kitty, ghostty
|
|
7
|
+
3. Half-block + truecolor downscale (``▀``) — any 24-bit colour terminal
|
|
8
|
+
|
|
9
|
+
Layer 3 is the universal floor: it resizes the image and prints one ``▀`` per
|
|
10
|
+
cell, packing two vertical pixels into each character (foreground = top pixel,
|
|
11
|
+
background = bottom pixel). Pixel-art sources render almost perfectly this way.
|
|
12
|
+
|
|
13
|
+
Used for the startup mascot banner, and reusable for ``/vision`` previews,
|
|
14
|
+
``/screenshot`` echoes, and inline quant charts.
|
|
15
|
+
|
|
16
|
+
CLI: python3 -m ui.image_render <path> [width] [--half|--iterm|--kitty]
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import base64
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from PIL import Image
|
|
27
|
+
_HAS_PIL = True
|
|
28
|
+
except Exception: # pragma: no cover - PIL is a hard dep for this module
|
|
29
|
+
_HAS_PIL = False
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ── Terminal capability detection ────────────────────────────────────────────
|
|
33
|
+
def _in_tmux() -> bool:
|
|
34
|
+
# Image protocols need passthrough wrapping inside tmux; play safe and fall
|
|
35
|
+
# back to half-blocks rather than spraying escape bytes the pane won't eat.
|
|
36
|
+
return bool(os.environ.get("TMUX"))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def supports_iterm() -> bool:
|
|
40
|
+
if _in_tmux():
|
|
41
|
+
return False
|
|
42
|
+
if os.environ.get("TERM_PROGRAM") in ("iTerm.app", "WezTerm", "ghostty"):
|
|
43
|
+
return True
|
|
44
|
+
return bool(os.environ.get("ITERM_SESSION_ID"))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def supports_kitty() -> bool:
|
|
48
|
+
if _in_tmux():
|
|
49
|
+
return False
|
|
50
|
+
if os.environ.get("KITTY_WINDOW_ID"):
|
|
51
|
+
return True
|
|
52
|
+
return os.environ.get("TERM") == "xterm-kitty"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def supports_truecolor() -> bool:
|
|
56
|
+
if os.environ.get("COLORTERM") in ("truecolor", "24bit"):
|
|
57
|
+
return True
|
|
58
|
+
# Most modern terminals are truecolor even without advertising it; only the
|
|
59
|
+
# genuinely ancient (TERM=dumb / linux console) are not.
|
|
60
|
+
return os.environ.get("TERM", "") not in ("", "dumb", "linux")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def best_method() -> str:
|
|
64
|
+
"""Return the best available render method for the current terminal."""
|
|
65
|
+
if supports_iterm():
|
|
66
|
+
return "iterm"
|
|
67
|
+
if supports_kitty():
|
|
68
|
+
return "kitty"
|
|
69
|
+
if supports_truecolor():
|
|
70
|
+
return "half"
|
|
71
|
+
return "none"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ── Protocol emitters ────────────────────────────────────────────────────────
|
|
75
|
+
def _iterm_sequence(png_bytes: bytes, cells_wide: int) -> str:
|
|
76
|
+
"""iTerm2 OSC 1337 inline image. Width in character cells, height auto."""
|
|
77
|
+
b64 = base64.b64encode(png_bytes).decode()
|
|
78
|
+
return (
|
|
79
|
+
f"\x1b]1337;File=inline=1;width={cells_wide};"
|
|
80
|
+
f"preserveAspectRatio=1:{b64}\x07"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _kitty_sequence(png_bytes: bytes, cells_wide: int) -> str:
|
|
85
|
+
"""Kitty graphics protocol, PNG payload (f=100), chunked at 4096 bytes."""
|
|
86
|
+
b64 = base64.b64encode(png_bytes).decode()
|
|
87
|
+
chunk = 4096
|
|
88
|
+
parts: list[str] = []
|
|
89
|
+
i = 0
|
|
90
|
+
first = True
|
|
91
|
+
while i < len(b64):
|
|
92
|
+
piece = b64[i : i + chunk]
|
|
93
|
+
i += chunk
|
|
94
|
+
more = 1 if i < len(b64) else 0
|
|
95
|
+
if first:
|
|
96
|
+
# a=T transmit+display, f=100 PNG, c=columns to scale into
|
|
97
|
+
ctrl = f"a=T,f=100,c={cells_wide},m={more}"
|
|
98
|
+
first = False
|
|
99
|
+
else:
|
|
100
|
+
ctrl = f"m={more}"
|
|
101
|
+
parts.append(f"\x1b_G{ctrl};{piece}\x1b\\")
|
|
102
|
+
return "".join(parts)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# ── Half-block fallback (universal) ──────────────────────────────────────────
|
|
106
|
+
_UPPER = "▀" # ▀ upper half block
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def autocrop(img: "Image.Image", tol: int = 18, pad: int = 1) -> "Image.Image":
|
|
110
|
+
"""Trim a uniform border (e.g. the robot's black canvas) so the subject
|
|
111
|
+
fills the frame. Background colour is sampled from the top-left pixel;
|
|
112
|
+
pixels within ``tol`` of it are treated as border. Falls back to the
|
|
113
|
+
original image if nothing distinct is found.
|
|
114
|
+
"""
|
|
115
|
+
from PIL import ImageChops, Image as _I
|
|
116
|
+
|
|
117
|
+
rgb = img.convert("RGB")
|
|
118
|
+
bg = _I.new("RGB", rgb.size, rgb.getpixel((0, 0)))
|
|
119
|
+
diff = ImageChops.difference(rgb, bg).convert("L")
|
|
120
|
+
box = diff.point(lambda p: 255 if p > tol else 0).getbbox()
|
|
121
|
+
if not box:
|
|
122
|
+
return img
|
|
123
|
+
l, t, r, b = box
|
|
124
|
+
l, t = max(0, l - pad), max(0, t - pad)
|
|
125
|
+
r, b = min(img.width, r + pad), min(img.height, b + pad)
|
|
126
|
+
return img.crop((l, t, r, b))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def half_block_render(img: "Image.Image", cells_wide: int = 36) -> str:
|
|
130
|
+
"""Render a PIL image as ``▀`` half-blocks with 24-bit colour.
|
|
131
|
+
|
|
132
|
+
Each character is 1 pixel wide and 2 pixels tall, so a square source maps to
|
|
133
|
+
``cells_wide`` columns × ``cells_wide // 2`` rows. Foreground paints the top
|
|
134
|
+
pixel, background the bottom one.
|
|
135
|
+
"""
|
|
136
|
+
img = img.convert("RGB")
|
|
137
|
+
w, h = img.size
|
|
138
|
+
# Sample at cells_wide × (2 px per row). Rows chosen to preserve aspect once
|
|
139
|
+
# the 1:2 cell shape is accounted for, so on-screen proportions match.
|
|
140
|
+
px_w = max(1, cells_wide)
|
|
141
|
+
px_h = max(2, round(px_w * h / w))
|
|
142
|
+
if px_h % 2:
|
|
143
|
+
px_h += 1 # even rows so every cell has a top+bottom pixel
|
|
144
|
+
small = img.resize((px_w, px_h), Image.LANCZOS)
|
|
145
|
+
px = small.load()
|
|
146
|
+
|
|
147
|
+
lines: list[str] = []
|
|
148
|
+
for row in range(0, px_h, 2):
|
|
149
|
+
cells: list[str] = []
|
|
150
|
+
for x in range(px_w):
|
|
151
|
+
tr, tg, tb = px[x, row]
|
|
152
|
+
br, bg, bb = px[x, row + 1]
|
|
153
|
+
cells.append(
|
|
154
|
+
f"\x1b[38;2;{tr};{tg};{tb};48;2;{br};{bg};{bb}m{_UPPER}"
|
|
155
|
+
)
|
|
156
|
+
cells.append("\x1b[0m")
|
|
157
|
+
lines.append("".join(cells))
|
|
158
|
+
return "\n".join(lines)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ── Public entry point ───────────────────────────────────────────────────────
|
|
162
|
+
def render_image(
|
|
163
|
+
path: str,
|
|
164
|
+
cells_wide: int = 36,
|
|
165
|
+
method: str | None = None,
|
|
166
|
+
crop: bool = True,
|
|
167
|
+
) -> str | None:
|
|
168
|
+
"""Return a printable string that draws ``path`` in the terminal.
|
|
169
|
+
|
|
170
|
+
``method`` forces one of ``iterm`` / ``kitty`` / ``half``; default auto-detects.
|
|
171
|
+
``crop`` trims a uniform border first so the subject fills the frame.
|
|
172
|
+
Returns ``None`` if the image can't be loaded or no method is usable.
|
|
173
|
+
"""
|
|
174
|
+
chosen = method or best_method()
|
|
175
|
+
|
|
176
|
+
# Protocol path (iTerm2/Kitty) only needs the raw PNG bytes — the terminal
|
|
177
|
+
# scales them. PIL is optional here: with it we autocrop so the subject
|
|
178
|
+
# fills the frame; without it we send the file as-is. This means the real
|
|
179
|
+
# image still shows even when Pillow isn't installed in the venv.
|
|
180
|
+
if chosen in ("iterm", "kitty"):
|
|
181
|
+
try:
|
|
182
|
+
if _HAS_PIL:
|
|
183
|
+
import io
|
|
184
|
+
|
|
185
|
+
img = Image.open(path)
|
|
186
|
+
if crop:
|
|
187
|
+
try:
|
|
188
|
+
img = autocrop(img)
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
buf = io.BytesIO()
|
|
192
|
+
img.convert("RGB").save(buf, format="PNG")
|
|
193
|
+
data = buf.getvalue()
|
|
194
|
+
else:
|
|
195
|
+
with open(path, "rb") as fh:
|
|
196
|
+
data = fh.read()
|
|
197
|
+
if chosen == "iterm":
|
|
198
|
+
return _iterm_sequence(data, cells_wide)
|
|
199
|
+
return _kitty_sequence(data, cells_wide)
|
|
200
|
+
except Exception:
|
|
201
|
+
chosen = "half" # fall through to the universal path
|
|
202
|
+
|
|
203
|
+
# Half-block fallback needs PIL to resize.
|
|
204
|
+
if chosen == "half" and _HAS_PIL:
|
|
205
|
+
try:
|
|
206
|
+
img = Image.open(path)
|
|
207
|
+
if crop:
|
|
208
|
+
try:
|
|
209
|
+
img = autocrop(img)
|
|
210
|
+
except Exception:
|
|
211
|
+
pass
|
|
212
|
+
return half_block_render(img, cells_wide)
|
|
213
|
+
except Exception:
|
|
214
|
+
return None
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _main(argv: list[str]) -> int:
|
|
219
|
+
args = [a for a in argv if not a.startswith("--")]
|
|
220
|
+
flags = {a for a in argv if a.startswith("--")}
|
|
221
|
+
if not args:
|
|
222
|
+
print("usage: python3 -m ui.image_render <image> [width] [--half|--iterm|--kitty]")
|
|
223
|
+
return 2
|
|
224
|
+
path = args[0]
|
|
225
|
+
width = int(args[1]) if len(args) > 1 and args[1].isdigit() else 36
|
|
226
|
+
method = None
|
|
227
|
+
if "--half" in flags:
|
|
228
|
+
method = "half"
|
|
229
|
+
elif "--iterm" in flags:
|
|
230
|
+
method = "iterm"
|
|
231
|
+
elif "--kitty" in flags:
|
|
232
|
+
method = "kitty"
|
|
233
|
+
out = render_image(path, width, method)
|
|
234
|
+
if out is None:
|
|
235
|
+
print(f"(cannot render {path}; method={method or best_method()})")
|
|
236
|
+
return 1
|
|
237
|
+
sys.stdout.write(out + "\n")
|
|
238
|
+
sys.stdout.flush()
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
raise SystemExit(_main(sys.argv[1:]))
|