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
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
aria_relay_client.py — 连接 Aria 中继服务器的 WebSocket 客户端
|
|
4
|
+
================================================================
|
|
5
|
+
本机运行,把中继服务器转发来的飞书消息交给 aria_feishu_bot 处理,
|
|
6
|
+
并把 LLM 回复返回给中继服务器,再由服务器推送到飞书。
|
|
7
|
+
|
|
8
|
+
启动方式:
|
|
9
|
+
python3 aria_relay_client.py # 单次连接(断线自动重连)
|
|
10
|
+
python3 aria_relay_client.py --once # 调试:收到第一条消息后退出
|
|
11
|
+
|
|
12
|
+
所需环境变量(读取 ~/.aria/.env):
|
|
13
|
+
ARIA_RELAY_URL wss://relay.aria.ai(或自建服务器地址)
|
|
14
|
+
ARIA_RELAY_CLIENT_ID setup_wizard 生成的 12 位 hex id
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("aria.relay_client")
|
|
28
|
+
|
|
29
|
+
# ── 加载 ~/.aria/.env ──────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
def _load_env() -> None:
|
|
32
|
+
env_file = Path.home() / ".aria" / ".env"
|
|
33
|
+
if env_file.exists():
|
|
34
|
+
for line in env_file.read_text().splitlines():
|
|
35
|
+
line = line.strip()
|
|
36
|
+
if line and not line.startswith("#") and "=" in line:
|
|
37
|
+
k, _, v = line.partition("=")
|
|
38
|
+
k = k.strip()
|
|
39
|
+
if k not in os.environ:
|
|
40
|
+
os.environ[k] = v.strip()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_load_env()
|
|
44
|
+
|
|
45
|
+
_RELAY_URL = os.environ.get("ARIA_RELAY_URL", "wss://relay.aria.ai")
|
|
46
|
+
_CLIENT_ID = os.environ.get("ARIA_RELAY_CLIENT_ID", "")
|
|
47
|
+
_RECONNECT_DELAY_MAX = 60 # seconds
|
|
48
|
+
_RECONNECT_DELAY_BASE = 3
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ── 本地 aria_feishu_bot import ───────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def _get_feishu_bot():
|
|
54
|
+
aria_dir = Path(__file__).parent
|
|
55
|
+
if str(aria_dir) not in sys.path:
|
|
56
|
+
sys.path.insert(0, str(aria_dir))
|
|
57
|
+
try:
|
|
58
|
+
import aria_feishu_bot
|
|
59
|
+
return aria_feishu_bot
|
|
60
|
+
except ImportError as e:
|
|
61
|
+
logger.warning("aria_feishu_bot not importable: %s", e)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ── Message handler ───────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
async def _handle_message(raw_msg: dict, ws) -> None:
|
|
68
|
+
"""
|
|
69
|
+
Server sends:
|
|
70
|
+
{"type": "message", "id": "req_xxx", "payload": <feishu_event_dict>}
|
|
71
|
+
|
|
72
|
+
We reply:
|
|
73
|
+
{"type": "response", "id": "req_xxx", "result": <any>}
|
|
74
|
+
"""
|
|
75
|
+
req_id = raw_msg.get("id", "")
|
|
76
|
+
payload = raw_msg.get("payload", {})
|
|
77
|
+
|
|
78
|
+
bot = _get_feishu_bot()
|
|
79
|
+
if bot is None:
|
|
80
|
+
result = {"error": "aria_feishu_bot unavailable"}
|
|
81
|
+
else:
|
|
82
|
+
try:
|
|
83
|
+
result = await bot.dispatch_event(payload)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.exception("dispatch_event error")
|
|
86
|
+
result = {"error": str(e)[:300]}
|
|
87
|
+
|
|
88
|
+
reply = json.dumps({"type": "response", "id": req_id, "result": result})
|
|
89
|
+
await ws.send(reply)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# ── Main loop ─────────────────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
async def _connect_and_serve(once: bool = False) -> None:
|
|
95
|
+
try:
|
|
96
|
+
import websockets # type: ignore
|
|
97
|
+
except ImportError:
|
|
98
|
+
logger.error("websockets package not installed — run: pip install websockets")
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
if not _CLIENT_ID:
|
|
102
|
+
logger.error(
|
|
103
|
+
"ARIA_RELAY_CLIENT_ID is not set. "
|
|
104
|
+
"Run setup_wizard.py to generate your client ID."
|
|
105
|
+
)
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
|
|
108
|
+
delay = _RECONNECT_DELAY_BASE
|
|
109
|
+
while True:
|
|
110
|
+
try:
|
|
111
|
+
logger.info("Connecting to %s (client_id=%s)", _RELAY_URL, _CLIENT_ID)
|
|
112
|
+
async with websockets.connect(
|
|
113
|
+
_RELAY_URL,
|
|
114
|
+
ping_interval=30,
|
|
115
|
+
ping_timeout=10,
|
|
116
|
+
open_timeout=15,
|
|
117
|
+
) as ws:
|
|
118
|
+
# Register with server
|
|
119
|
+
await ws.send(json.dumps({
|
|
120
|
+
"type": "register",
|
|
121
|
+
"client_id": _CLIENT_ID,
|
|
122
|
+
}))
|
|
123
|
+
ack_raw = await asyncio.wait_for(ws.recv(), timeout=10)
|
|
124
|
+
ack = json.loads(ack_raw)
|
|
125
|
+
if not ack.get("ok"):
|
|
126
|
+
logger.error("Registration rejected: %s", ack.get("reason", "unknown"))
|
|
127
|
+
await asyncio.sleep(delay)
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
logger.info("Registered. Waiting for messages…")
|
|
131
|
+
delay = _RECONNECT_DELAY_BASE # reset on success
|
|
132
|
+
|
|
133
|
+
async for raw in ws:
|
|
134
|
+
try:
|
|
135
|
+
msg = json.loads(raw)
|
|
136
|
+
except json.JSONDecodeError:
|
|
137
|
+
logger.warning("Invalid JSON from relay: %r", raw[:100])
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
if msg.get("type") == "ping":
|
|
141
|
+
await ws.send(json.dumps({"type": "pong"}))
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
if msg.get("type") == "message":
|
|
145
|
+
asyncio.create_task(_handle_message(msg, ws))
|
|
146
|
+
|
|
147
|
+
if once:
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
except (OSError, ConnectionRefusedError) as e:
|
|
151
|
+
logger.warning("Connection failed: %s — retry in %ds", e, delay)
|
|
152
|
+
except asyncio.CancelledError:
|
|
153
|
+
logger.info("Relay client cancelled")
|
|
154
|
+
return
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.warning("Relay error: %s — retry in %ds", e, delay)
|
|
157
|
+
|
|
158
|
+
await asyncio.sleep(delay)
|
|
159
|
+
delay = min(delay * 2, _RECONNECT_DELAY_MAX)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def main() -> None:
|
|
163
|
+
import argparse
|
|
164
|
+
parser = argparse.ArgumentParser(description="Aria 中继客户端")
|
|
165
|
+
parser.add_argument("--once", action="store_true", help="接收一条消息后退出(调试用)")
|
|
166
|
+
parser.add_argument("--verbose", "-v", action="store_true")
|
|
167
|
+
args = parser.parse_args()
|
|
168
|
+
|
|
169
|
+
logging.basicConfig(
|
|
170
|
+
level=logging.DEBUG if args.verbose else logging.INFO,
|
|
171
|
+
format="%(asctime)s [aria-relay] %(message)s",
|
|
172
|
+
datefmt="%H:%M:%S",
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
asyncio.run(_connect_and_serve(once=args.once))
|
|
177
|
+
except KeyboardInterrupt:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
main()
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Canva Connect API client — OAuth2 + Autofill (report design drafts).
|
|
2
|
+
|
|
3
|
+
Canva has no headless "create a design from scratch" API — Autofill is the
|
|
4
|
+
real capability: fill a brand template you already made in the Canva app
|
|
5
|
+
with data, and export the result. That's the only "generate a report design"
|
|
6
|
+
path Canva actually supports server-side, so that's all this module does.
|
|
7
|
+
|
|
8
|
+
One-time setup (outside this code, on Canva's side):
|
|
9
|
+
1. Register a Connect app at https://www.canva.com/developers/ — get a
|
|
10
|
+
client_id / client_secret.
|
|
11
|
+
2. Create a brand template in Canva with the fields you want to autofill,
|
|
12
|
+
note its template_id.
|
|
13
|
+
3. Run `/canva connect` in aria-code once to do the OAuth handshake.
|
|
14
|
+
|
|
15
|
+
Token storage follows the same providers.json pattern as other data-service
|
|
16
|
+
keys (apps/cli/config_paths.py's providers_file, "data" section), extended
|
|
17
|
+
with refresh_token/expires_at — this is the first OAuth-based integration in
|
|
18
|
+
the codebase, so the refresh-before-expiry logic below is new, not reused
|
|
19
|
+
from elsewhere.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import base64
|
|
25
|
+
import hashlib
|
|
26
|
+
import http.server
|
|
27
|
+
import json
|
|
28
|
+
import secrets
|
|
29
|
+
import time
|
|
30
|
+
import urllib.parse
|
|
31
|
+
import webbrowser
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any, Dict, Optional
|
|
34
|
+
|
|
35
|
+
AUTH_URL = "https://www.canva.com/api/oauth/authorize"
|
|
36
|
+
TOKEN_URL = "https://api.canva.com/rest/v1/oauth/token"
|
|
37
|
+
API_BASE = "https://api.canva.com/rest/v1"
|
|
38
|
+
REDIRECT_PORT = 53219
|
|
39
|
+
REDIRECT_URI = f"http://127.0.0.1:{REDIRECT_PORT}/callback"
|
|
40
|
+
SCOPES = "design:content:write design:content:read"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _providers_path() -> Path:
|
|
44
|
+
from apps.cli.config_paths import resolve_paths
|
|
45
|
+
return resolve_paths().providers_file
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _load_canva_config() -> Dict[str, Any]:
|
|
49
|
+
path = _providers_path()
|
|
50
|
+
if not path.exists():
|
|
51
|
+
return {}
|
|
52
|
+
try:
|
|
53
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
54
|
+
except Exception:
|
|
55
|
+
return {}
|
|
56
|
+
return raw.get("data", {}).get("canva", {}) or {}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _save_canva_config(entry: Dict[str, Any]) -> None:
|
|
60
|
+
path = _providers_path()
|
|
61
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
62
|
+
existing: Dict[str, Any] = {}
|
|
63
|
+
if path.exists():
|
|
64
|
+
try:
|
|
65
|
+
existing = json.loads(path.read_text(encoding="utf-8"))
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
data_section = existing.get("data", {})
|
|
69
|
+
data_section["canva"] = entry
|
|
70
|
+
existing["data"] = data_section
|
|
71
|
+
path.write_text(json.dumps(existing, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _CallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
75
|
+
code: Optional[str] = None
|
|
76
|
+
error: Optional[str] = None
|
|
77
|
+
|
|
78
|
+
def do_GET(self): # noqa: N802
|
|
79
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
80
|
+
qs = urllib.parse.parse_qs(parsed.query)
|
|
81
|
+
_CallbackHandler.code = qs.get("code", [None])[0]
|
|
82
|
+
_CallbackHandler.error = qs.get("error", [None])[0]
|
|
83
|
+
self.send_response(200)
|
|
84
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
85
|
+
self.end_headers()
|
|
86
|
+
msg = "授权成功,可以关闭这个标签页了。" if _CallbackHandler.code else f"授权失败: {_CallbackHandler.error}"
|
|
87
|
+
self.wfile.write(f"<html><body><p>{msg}</p></body></html>".encode("utf-8"))
|
|
88
|
+
|
|
89
|
+
def log_message(self, *args):
|
|
90
|
+
pass # silence default stderr logging
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def connect(client_id: str, client_secret: str, *, timeout: float = 120.0) -> Dict[str, Any]:
|
|
94
|
+
"""Run the OAuth2 authorization-code + PKCE flow, store tokens, return the saved entry."""
|
|
95
|
+
verifier = secrets.token_urlsafe(64)[:64]
|
|
96
|
+
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).decode().rstrip("=")
|
|
97
|
+
state = secrets.token_urlsafe(16)
|
|
98
|
+
|
|
99
|
+
params = {
|
|
100
|
+
"response_type": "code",
|
|
101
|
+
"client_id": client_id,
|
|
102
|
+
"redirect_uri": REDIRECT_URI,
|
|
103
|
+
"scope": SCOPES,
|
|
104
|
+
"state": state,
|
|
105
|
+
"code_challenge": challenge,
|
|
106
|
+
"code_challenge_method": "S256",
|
|
107
|
+
}
|
|
108
|
+
url = f"{AUTH_URL}?{urllib.parse.urlencode(params)}"
|
|
109
|
+
|
|
110
|
+
_CallbackHandler.code = None
|
|
111
|
+
_CallbackHandler.error = None
|
|
112
|
+
server = http.server.HTTPServer(("127.0.0.1", REDIRECT_PORT), _CallbackHandler)
|
|
113
|
+
server.timeout = timeout
|
|
114
|
+
|
|
115
|
+
webbrowser.open(url)
|
|
116
|
+
deadline = time.time() + timeout
|
|
117
|
+
while _CallbackHandler.code is None and _CallbackHandler.error is None and time.time() < deadline:
|
|
118
|
+
server.handle_request()
|
|
119
|
+
server.server_close()
|
|
120
|
+
|
|
121
|
+
if _CallbackHandler.error:
|
|
122
|
+
return {"success": False, "error": _CallbackHandler.error}
|
|
123
|
+
if not _CallbackHandler.code:
|
|
124
|
+
return {"success": False, "error": f"Timed out waiting for Canva authorization (>{timeout:.0f}s)."}
|
|
125
|
+
|
|
126
|
+
import requests
|
|
127
|
+
|
|
128
|
+
resp = requests.post(
|
|
129
|
+
TOKEN_URL,
|
|
130
|
+
data={
|
|
131
|
+
"grant_type": "authorization_code",
|
|
132
|
+
"code": _CallbackHandler.code,
|
|
133
|
+
"redirect_uri": REDIRECT_URI,
|
|
134
|
+
"code_verifier": verifier,
|
|
135
|
+
},
|
|
136
|
+
auth=(client_id, client_secret),
|
|
137
|
+
timeout=15,
|
|
138
|
+
)
|
|
139
|
+
if resp.status_code != 200:
|
|
140
|
+
return {"success": False, "error": f"Token exchange failed: {resp.status_code} {resp.text[:300]}"}
|
|
141
|
+
tok = resp.json()
|
|
142
|
+
entry = {
|
|
143
|
+
"client_id": client_id,
|
|
144
|
+
"client_secret": client_secret,
|
|
145
|
+
"access_token": tok["access_token"],
|
|
146
|
+
"refresh_token": tok.get("refresh_token", ""),
|
|
147
|
+
"expires_at": time.time() + float(tok.get("expires_in", 3600)),
|
|
148
|
+
}
|
|
149
|
+
_save_canva_config(entry)
|
|
150
|
+
return {"success": True}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _refresh_if_needed(entry: Dict[str, Any]) -> Dict[str, Any]:
|
|
154
|
+
if float(entry.get("expires_at", 0)) - time.time() > 60:
|
|
155
|
+
return entry
|
|
156
|
+
if not entry.get("refresh_token"):
|
|
157
|
+
raise RuntimeError("Canva access token expired and no refresh_token stored — run /canva connect again.")
|
|
158
|
+
import requests
|
|
159
|
+
|
|
160
|
+
resp = requests.post(
|
|
161
|
+
TOKEN_URL,
|
|
162
|
+
data={"grant_type": "refresh_token", "refresh_token": entry["refresh_token"]},
|
|
163
|
+
auth=(entry["client_id"], entry["client_secret"]),
|
|
164
|
+
timeout=15,
|
|
165
|
+
)
|
|
166
|
+
resp.raise_for_status()
|
|
167
|
+
tok = resp.json()
|
|
168
|
+
entry = {
|
|
169
|
+
**entry,
|
|
170
|
+
"access_token": tok["access_token"],
|
|
171
|
+
"refresh_token": tok.get("refresh_token", entry["refresh_token"]),
|
|
172
|
+
"expires_at": time.time() + float(tok.get("expires_in", 3600)),
|
|
173
|
+
}
|
|
174
|
+
_save_canva_config(entry)
|
|
175
|
+
return entry
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _access_token() -> str:
|
|
179
|
+
entry = _load_canva_config()
|
|
180
|
+
if not entry.get("access_token"):
|
|
181
|
+
raise RuntimeError("Canva not connected — run /canva connect first.")
|
|
182
|
+
entry = _refresh_if_needed(entry)
|
|
183
|
+
return entry["access_token"]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def upload_asset(file_path: str, *, poll_timeout: float = 60.0) -> Dict[str, Any]:
|
|
187
|
+
"""Upload a local image/video file (e.g. a report chart PNG) to Canva and
|
|
188
|
+
return its asset_id, for use as an {"type": "image", "asset_id": ...}
|
|
189
|
+
value in autofill_design()'s `data`.
|
|
190
|
+
|
|
191
|
+
Blocking (uses requests) — same threading note as autofill_design.
|
|
192
|
+
"""
|
|
193
|
+
import base64
|
|
194
|
+
import json
|
|
195
|
+
import requests
|
|
196
|
+
|
|
197
|
+
path = Path(file_path)
|
|
198
|
+
if not path.exists():
|
|
199
|
+
return {"success": False, "error": f"File not found: {file_path}"}
|
|
200
|
+
|
|
201
|
+
token = _access_token()
|
|
202
|
+
name_b64 = base64.b64encode(path.name.encode("utf-8")).decode("ascii")
|
|
203
|
+
headers = {
|
|
204
|
+
"Authorization": f"Bearer {token}",
|
|
205
|
+
"Content-Type": "application/octet-stream",
|
|
206
|
+
"Asset-Upload-Metadata": json.dumps({"name_base64": name_b64}),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
create_resp = requests.post(
|
|
210
|
+
f"{API_BASE}/asset-uploads",
|
|
211
|
+
headers=headers,
|
|
212
|
+
data=path.read_bytes(),
|
|
213
|
+
timeout=30,
|
|
214
|
+
)
|
|
215
|
+
if create_resp.status_code not in (200, 201, 202):
|
|
216
|
+
return {"success": False, "error": f"Asset upload failed: {create_resp.status_code} {create_resp.text[:300]}"}
|
|
217
|
+
job = create_resp.json().get("job", {})
|
|
218
|
+
job_id = job.get("id", "")
|
|
219
|
+
if not job_id:
|
|
220
|
+
return {"success": False, "error": "Canva did not return an asset-upload job id"}
|
|
221
|
+
|
|
222
|
+
poll_headers = {"Authorization": f"Bearer {token}"}
|
|
223
|
+
deadline = time.time() + poll_timeout
|
|
224
|
+
while time.time() < deadline:
|
|
225
|
+
poll_resp = requests.get(f"{API_BASE}/asset-uploads/{job_id}", headers=poll_headers, timeout=15)
|
|
226
|
+
poll_resp.raise_for_status()
|
|
227
|
+
job = poll_resp.json().get("job", {})
|
|
228
|
+
status = job.get("status", "")
|
|
229
|
+
if status == "success":
|
|
230
|
+
asset = job.get("asset", {})
|
|
231
|
+
return {"success": True, "asset_id": asset.get("id", ""), "name": asset.get("name", "")}
|
|
232
|
+
if status == "failed":
|
|
233
|
+
return {"success": False, "error": job.get("error", {}).get("message", "asset upload job failed")}
|
|
234
|
+
time.sleep(2)
|
|
235
|
+
return {"success": False, "error": f"Asset upload job timed out after {poll_timeout:.0f}s (job_id={job_id})"}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def autofill_design(template_id: str, data: Dict[str, Any], *, poll_timeout: float = 60.0) -> Dict[str, Any]:
|
|
239
|
+
"""Fill a Canva brand template with data and return the exported design.
|
|
240
|
+
|
|
241
|
+
`data` values must be typed DatasetValue objects per the Canva Autofill
|
|
242
|
+
API, not bare strings, e.g.:
|
|
243
|
+
{"headline": {"type": "text", "text": "Q3 Report"},
|
|
244
|
+
"chart_img": {"type": "image", "asset_id": "<uploaded-asset-id>"}}
|
|
245
|
+
Supported value "type"s: text, image, video, chart, sheet.
|
|
246
|
+
|
|
247
|
+
Blocking (uses requests) — callers on an event loop should run this in
|
|
248
|
+
an executor, same as every other broker/report call in this codebase.
|
|
249
|
+
"""
|
|
250
|
+
import requests
|
|
251
|
+
|
|
252
|
+
token = _access_token()
|
|
253
|
+
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
254
|
+
|
|
255
|
+
create_resp = requests.post(
|
|
256
|
+
f"{API_BASE}/autofills",
|
|
257
|
+
headers=headers,
|
|
258
|
+
json={"type": "create_from_brand_template", "brand_template_id": template_id, "data": data},
|
|
259
|
+
timeout=15,
|
|
260
|
+
)
|
|
261
|
+
if create_resp.status_code not in (200, 201, 202):
|
|
262
|
+
return {"success": False, "error": f"Autofill job creation failed: {create_resp.status_code} {create_resp.text[:300]}"}
|
|
263
|
+
job = create_resp.json().get("job", {})
|
|
264
|
+
job_id = job.get("id", "")
|
|
265
|
+
if not job_id:
|
|
266
|
+
return {"success": False, "error": "Canva did not return a job id"}
|
|
267
|
+
|
|
268
|
+
deadline = time.time() + poll_timeout
|
|
269
|
+
while time.time() < deadline:
|
|
270
|
+
poll_resp = requests.get(f"{API_BASE}/autofills/{job_id}", headers=headers, timeout=15)
|
|
271
|
+
poll_resp.raise_for_status()
|
|
272
|
+
job = poll_resp.json().get("job", {})
|
|
273
|
+
status = job.get("status", "")
|
|
274
|
+
if status == "success":
|
|
275
|
+
result = job.get("result", {})
|
|
276
|
+
return {
|
|
277
|
+
"success": True,
|
|
278
|
+
"design_id": result.get("design", {}).get("id", ""),
|
|
279
|
+
"design_url": result.get("design", {}).get("url", ""),
|
|
280
|
+
}
|
|
281
|
+
if status == "failed":
|
|
282
|
+
return {"success": False, "error": job.get("error", {}).get("message", "autofill job failed")}
|
|
283
|
+
time.sleep(2)
|
|
284
|
+
return {"success": False, "error": f"Autofill job timed out after {poll_timeout:.0f}s (job_id={job_id})"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""figma_client.py — read-only Figma REST API client.
|
|
2
|
+
|
|
3
|
+
Figma has no public API for creating/editing designs from a server-side
|
|
4
|
+
script — only the in-app Plugin API can do that, which requires a human
|
|
5
|
+
running it inside Figma itself. So this module only reads: file structure
|
|
6
|
+
(page/frame names, not the full multi-MB node tree) and comments. Nothing
|
|
7
|
+
here writes to Figma.
|
|
8
|
+
|
|
9
|
+
Auth: a Personal Access Token, stored the same way as every other data-service
|
|
10
|
+
key in this codebase — /apikey set figma <token> (see aria_cli.py's
|
|
11
|
+
_DATA_KEY_MAP), read back via _get_provider_key("figma")/env FIGMA_API_KEY.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
API_BASE = "https://api.figma.com/v1"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _token() -> str:
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
token = os.getenv("FIGMA_API_KEY", "")
|
|
25
|
+
if token:
|
|
26
|
+
return token
|
|
27
|
+
try:
|
|
28
|
+
from apps.cli.config_paths import resolve_paths
|
|
29
|
+
import json
|
|
30
|
+
|
|
31
|
+
path = resolve_paths().providers_file
|
|
32
|
+
if path.exists():
|
|
33
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
34
|
+
entry = raw.get("data", {}).get("figma", {})
|
|
35
|
+
if entry.get("api_key"):
|
|
36
|
+
return entry["api_key"]
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
return ""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _headers() -> Dict[str, str]:
|
|
43
|
+
token = _token()
|
|
44
|
+
if not token:
|
|
45
|
+
raise RuntimeError("Figma not configured — run /apikey set figma <personal_access_token> first.")
|
|
46
|
+
return {"X-Figma-Token": token}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _summarize_node(node: Dict[str, Any], depth: int) -> Dict[str, Any]:
|
|
50
|
+
summary: Dict[str, Any] = {"name": node.get("name", ""), "type": node.get("type", "")}
|
|
51
|
+
children = node.get("children") or []
|
|
52
|
+
if children and depth > 0:
|
|
53
|
+
summary["children"] = [_summarize_node(c, depth - 1) for c in children]
|
|
54
|
+
elif children:
|
|
55
|
+
summary["child_count"] = len(children)
|
|
56
|
+
return summary
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_file_summary(file_key: str, *, depth: int = 2) -> Dict[str, Any]:
|
|
60
|
+
"""Return {name, last_modified, pages: [{name, top_level_nodes: [...]}]} for a Figma file.
|
|
61
|
+
|
|
62
|
+
Depth-limited on purpose: a raw Figma file's `document` tree can be
|
|
63
|
+
megabytes of deeply nested nodes — this returns page names and the
|
|
64
|
+
first `depth` levels of each page's structure, not the full document.
|
|
65
|
+
"""
|
|
66
|
+
import requests
|
|
67
|
+
|
|
68
|
+
resp = requests.get(f"{API_BASE}/files/{file_key}", headers=_headers(), timeout=20)
|
|
69
|
+
if resp.status_code != 200:
|
|
70
|
+
return {"success": False, "error": f"Figma API error {resp.status_code}: {resp.text[:300]}"}
|
|
71
|
+
data = resp.json()
|
|
72
|
+
document = data.get("document", {})
|
|
73
|
+
pages = [_summarize_node(page, depth) for page in document.get("children", [])]
|
|
74
|
+
return {
|
|
75
|
+
"success": True,
|
|
76
|
+
"name": data.get("name", ""),
|
|
77
|
+
"last_modified": data.get("lastModified", ""),
|
|
78
|
+
"pages": pages,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def list_comments(file_key: str) -> Dict[str, Any]:
|
|
83
|
+
import requests
|
|
84
|
+
|
|
85
|
+
resp = requests.get(f"{API_BASE}/files/{file_key}/comments", headers=_headers(), timeout=20)
|
|
86
|
+
if resp.status_code != 200:
|
|
87
|
+
return {"success": False, "error": f"Figma API error {resp.status_code}: {resp.text[:300]}"}
|
|
88
|
+
comments = resp.json().get("comments", [])
|
|
89
|
+
return {
|
|
90
|
+
"success": True,
|
|
91
|
+
"comments": [
|
|
92
|
+
{
|
|
93
|
+
"message": c.get("message", ""),
|
|
94
|
+
"user": (c.get("user") or {}).get("handle", ""),
|
|
95
|
+
"created_at": c.get("created_at", ""),
|
|
96
|
+
"resolved_at": c.get("resolved_at"),
|
|
97
|
+
}
|
|
98
|
+
for c in comments
|
|
99
|
+
],
|
|
100
|
+
}
|