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.
Files changed (478) hide show
  1. aria_code/__init__.py +42 -0
  2. aria_code/_version.py +20 -0
  3. aria_code/adk_apps/__init__.py +1 -0
  4. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  5. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  6. aria_code/adk_apps/aria_research/__init__.py +1 -0
  7. aria_code/adk_apps/aria_research/agent.py +31 -0
  8. aria_code/agents/__init__.py +34 -0
  9. aria_code/agents/base.py +318 -0
  10. aria_code/agents/code_review.py +200 -0
  11. aria_code/agents/deep/__init__.py +37 -0
  12. aria_code/agents/deep/calibration_loop.py +144 -0
  13. aria_code/agents/deep/critic.py +125 -0
  14. aria_code/agents/deep/deepen.py +193 -0
  15. aria_code/agents/deep/models.py +149 -0
  16. aria_code/agents/deep/pipeline.py +164 -0
  17. aria_code/agents/deep/quant_fusion.py +192 -0
  18. aria_code/agents/deep/themes.py +95 -0
  19. aria_code/agents/deep/tiers.py +106 -0
  20. aria_code/agents/engineering/__init__.py +14 -0
  21. aria_code/agents/engineering/coder.py +222 -0
  22. aria_code/agents/engineering/tester.py +145 -0
  23. aria_code/agents/financial/__init__.py +21 -0
  24. aria_code/agents/financial/cashflow_burn.py +119 -0
  25. aria_code/agents/financial/catalyst.py +279 -0
  26. aria_code/agents/financial/corporate_finance.py +175 -0
  27. aria_code/agents/financial/debate.py +224 -0
  28. aria_code/agents/financial/earnings.py +303 -0
  29. aria_code/agents/financial/fundamental.py +191 -0
  30. aria_code/agents/financial/macro.py +99 -0
  31. aria_code/agents/financial/news.py +207 -0
  32. aria_code/agents/financial/risk.py +165 -0
  33. aria_code/agents/financial/sector.py +279 -0
  34. aria_code/agents/financial/strategist.py +121 -0
  35. aria_code/agents/financial/stripe_revenue.py +127 -0
  36. aria_code/agents/financial/synthesis.py +274 -0
  37. aria_code/agents/financial/technical.py +258 -0
  38. aria_code/agents/orchestrator.py +44 -0
  39. aria_code/agents/portfolio_agent.py +363 -0
  40. aria_code/agents/realty/__init__.py +62 -0
  41. aria_code/agents/realty/asset_diagnosis.py +152 -0
  42. aria_code/agents/realty/business_match.py +166 -0
  43. aria_code/agents/realty/cashflow_verify.py +209 -0
  44. aria_code/agents/realty/contract_rules.py +210 -0
  45. aria_code/agents/realty/energy_anomaly.py +189 -0
  46. aria_code/agents/realty/exit_settlement.py +208 -0
  47. aria_code/agents/realty/fulfillment_risk.py +206 -0
  48. aria_code/agents/realty/ops_optimize.py +160 -0
  49. aria_code/agents/realty/revenue_share.py +215 -0
  50. aria_code/agents/registry.py +158 -0
  51. aria_code/agents/signal_scheme.py +111 -0
  52. aria_code/agents/sports/__init__.py +0 -0
  53. aria_code/agents/sports/football_agent.py +197 -0
  54. aria_code/agents/supervisor.py +113 -0
  55. aria_code/agents/team.py +492 -0
  56. aria_code/agents/warehouse/__init__.py +25 -0
  57. aria_code/agents/warehouse/contracts.py +26 -0
  58. aria_code/agents/warehouse/dashboard.py +155 -0
  59. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  60. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  61. aria_code/agents/warehouse/inventory_health.py +24 -0
  62. aria_code/agents/warehouse/local_run.py +71 -0
  63. aria_code/agents/warehouse/local_snapshot.py +132 -0
  64. aria_code/agents/warehouse/logistics_cost.py +84 -0
  65. aria_code/agents/warehouse/logistics_sync.py +21 -0
  66. aria_code/agents/warehouse/workflow.py +31 -0
  67. aria_code/aliyun_data_client.py +4 -0
  68. aria_code/apps/README.md +12 -0
  69. aria_code/apps/__init__.py +2 -0
  70. aria_code/apps/channels/README.md +15 -0
  71. aria_code/apps/channels/__init__.py +17 -0
  72. aria_code/apps/channels/intake.py +92 -0
  73. aria_code/apps/channels/registry.py +95 -0
  74. aria_code/apps/channels/tradingview.py +94 -0
  75. aria_code/apps/cli/README.md +13 -0
  76. aria_code/apps/cli/__init__.py +2 -0
  77. aria_code/apps/cli/_optional.py +120 -0
  78. aria_code/apps/cli/bootstrap.py +145 -0
  79. aria_code/apps/cli/broker_render.py +206 -0
  80. aria_code/apps/cli/codegen_paths.py +29 -0
  81. aria_code/apps/cli/commands/__init__.py +16 -0
  82. aria_code/apps/cli/commands/_business_render.py +78 -0
  83. aria_code/apps/cli/commands/_ui.py +71 -0
  84. aria_code/apps/cli/commands/analysis_cmds.py +369 -0
  85. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  86. aria_code/apps/cli/commands/auth_cmds.py +239 -0
  87. aria_code/apps/cli/commands/backtest_cmds.py +2543 -0
  88. aria_code/apps/cli/commands/broker_cmds.py +1234 -0
  89. aria_code/apps/cli/commands/business_workflow_cmds.py +319 -0
  90. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  91. aria_code/apps/cli/commands/catalog.py +87 -0
  92. aria_code/apps/cli/commands/core_cmds.py +2520 -0
  93. aria_code/apps/cli/commands/data_cmds.py +457 -0
  94. aria_code/apps/cli/commands/diagnostic_cmds.py +230 -0
  95. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  96. aria_code/apps/cli/commands/file_cmds.py +312 -0
  97. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  98. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  99. aria_code/apps/cli/commands/market.py +434 -0
  100. aria_code/apps/cli/commands/market_cmds.py +1381 -0
  101. aria_code/apps/cli/commands/market_context.py +425 -0
  102. aria_code/apps/cli/commands/market_render.py +7 -0
  103. aria_code/apps/cli/commands/model_cmds.py +2153 -0
  104. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  105. aria_code/apps/cli/commands/orchestrator_cmds.py +179 -0
  106. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  107. aria_code/apps/cli/commands/portfolio_cmds.py +1278 -0
  108. aria_code/apps/cli/commands/report.py +437 -0
  109. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  110. aria_code/apps/cli/commands/session_cmds.py +236 -0
  111. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  112. aria_code/apps/cli/commands/team.py +669 -0
  113. aria_code/apps/cli/commands/team_render.py +8 -0
  114. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  115. aria_code/apps/cli/commands/warehouse_cmds.py +79 -0
  116. aria_code/apps/cli/commands/workflow_cmds.py +480 -0
  117. aria_code/apps/cli/commands/workspace_cmds.py +1494 -0
  118. aria_code/apps/cli/config_paths.py +73 -0
  119. aria_code/apps/cli/config_store.py +78 -0
  120. aria_code/apps/cli/constants.py +88 -0
  121. aria_code/apps/cli/context.py +34 -0
  122. aria_code/apps/cli/deterministic.py +191 -0
  123. aria_code/apps/cli/direct.py +48 -0
  124. aria_code/apps/cli/finance_service_catalog.py +111 -0
  125. aria_code/apps/cli/football_reports.py +231 -0
  126. aria_code/apps/cli/github_app_auth.py +135 -0
  127. aria_code/apps/cli/google_login.py +196 -0
  128. aria_code/apps/cli/handlers/__init__.py +11 -0
  129. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  130. aria_code/apps/cli/handlers/chart_handlers.py +1309 -0
  131. aria_code/apps/cli/handlers/market_handlers.py +2513 -0
  132. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  133. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  134. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  135. aria_code/apps/cli/helpers.py +302 -0
  136. aria_code/apps/cli/hooks.py +180 -0
  137. aria_code/apps/cli/i18n.py +297 -0
  138. aria_code/apps/cli/intent.py +136 -0
  139. aria_code/apps/cli/intent_router.py +276 -0
  140. aria_code/apps/cli/intent_signals.py +344 -0
  141. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  142. aria_code/apps/cli/local_tool_schemas.py +536 -0
  143. aria_code/apps/cli/main.py +29 -0
  144. aria_code/apps/cli/market_metadata.py +135 -0
  145. aria_code/apps/cli/market_universe.py +487 -0
  146. aria_code/apps/cli/message_processing.py +310 -0
  147. aria_code/apps/cli/model_catalog.py +620 -0
  148. aria_code/apps/cli/pdf_report.py +977 -0
  149. aria_code/apps/cli/plan_mode.py +139 -0
  150. aria_code/apps/cli/plotly_html.py +15 -0
  151. aria_code/apps/cli/prediction_feedback.py +202 -0
  152. aria_code/apps/cli/preflight.py +502 -0
  153. aria_code/apps/cli/project_aria.py +60 -0
  154. aria_code/apps/cli/prompt_assembly.py +59 -0
  155. aria_code/apps/cli/prompts/__init__.py +0 -0
  156. aria_code/apps/cli/prompts/coding.py +53 -0
  157. aria_code/apps/cli/prompts/select.py +142 -0
  158. aria_code/apps/cli/prompts/system_prompts.py +552 -0
  159. aria_code/apps/cli/prompts/ui.py +436 -0
  160. aria_code/apps/cli/provider_endpoints.py +237 -0
  161. aria_code/apps/cli/provider_keys.py +73 -0
  162. aria_code/apps/cli/providers/__init__.py +1 -0
  163. aria_code/apps/cli/providers/base.py +521 -0
  164. aria_code/apps/cli/providers/chat_routing.py +128 -0
  165. aria_code/apps/cli/providers/collaboration.py +98 -0
  166. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  167. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  168. aria_code/apps/cli/providers/llm/ollama_stream.py +1594 -0
  169. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  170. aria_code/apps/cli/providers/llm/sse_stream.py +257 -0
  171. aria_code/apps/cli/providers/runtime_bridge.py +353 -0
  172. aria_code/apps/cli/providers/vertexai_stream.py +316 -0
  173. aria_code/apps/cli/runtime_consumer.py +657 -0
  174. aria_code/apps/cli/sandbox.py +32 -0
  175. aria_code/apps/cli/session_export.py +120 -0
  176. aria_code/apps/cli/session_jsonl.py +208 -0
  177. aria_code/apps/cli/session_store.py +112 -0
  178. aria_code/apps/cli/skills_catalog.py +273 -0
  179. aria_code/apps/cli/todo_tracker.py +190 -0
  180. aria_code/apps/cli/tool_executor.py +823 -0
  181. aria_code/apps/cli/tool_registry.py +34 -0
  182. aria_code/apps/cli/tool_scope.py +106 -0
  183. aria_code/apps/cli/tools/__init__.py +40 -0
  184. aria_code/apps/cli/tools/context.py +46 -0
  185. aria_code/apps/cli/tools/file_tools.py +180 -0
  186. aria_code/apps/cli/tools/market_tools.py +620 -0
  187. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  188. aria_code/apps/cli/tools/system_tools.py +726 -0
  189. aria_code/apps/cli/tools/write_tools.py +845 -0
  190. aria_code/apps/cli/tradingview_bridge.py +529 -0
  191. aria_code/apps/cli/turn_planning.py +137 -0
  192. aria_code/apps/cli/update_check.py +153 -0
  193. aria_code/apps/cli/utils/__init__.py +0 -0
  194. aria_code/apps/cli/utils/market_detect.py +1597 -0
  195. aria_code/apps/daemon/README.md +14 -0
  196. aria_code/apps/vscode/README.md +115 -0
  197. aria_code/apps/vscode/package.json +70 -0
  198. aria_code/aria_cli.py +6743 -0
  199. aria_code/aria_daemon.py +1398 -0
  200. aria_code/aria_feishu_bot.py +1423 -0
  201. aria_code/aria_mcp_server.py +7 -0
  202. aria_code/aria_relay_client.py +4 -0
  203. aria_code/aria_relay_server.py +490 -0
  204. aria_code/aria_telegram_bot.py +202 -0
  205. aria_code/ariarc.py +385 -0
  206. aria_code/artifacts.py +581 -0
  207. aria_code/backtest_engine.py +4 -0
  208. aria_code/backtest_report.py +4 -0
  209. aria_code/brokers/__init__.py +78 -0
  210. aria_code/brokers/automation.py +193 -0
  211. aria_code/brokers/base.py +231 -0
  212. aria_code/brokers/capabilities.py +264 -0
  213. aria_code/brokers/cn/__init__.py +10 -0
  214. aria_code/brokers/cn/easytrader_broker.py +193 -0
  215. aria_code/brokers/cn/futu_broker.py +194 -0
  216. aria_code/brokers/cn/longbridge_broker.py +190 -0
  217. aria_code/brokers/cn/tiger_broker.py +196 -0
  218. aria_code/brokers/cn/xtquant_broker.py +225 -0
  219. aria_code/brokers/config.py +414 -0
  220. aria_code/brokers/intl/__init__.py +5 -0
  221. aria_code/brokers/intl/alpaca_broker.py +183 -0
  222. aria_code/brokers/intl/ibkr_broker.py +215 -0
  223. aria_code/brokers/intl/trading212_broker.py +183 -0
  224. aria_code/brokers/intl/webull_broker.py +156 -0
  225. aria_code/brokers/paper_broker.py +266 -0
  226. aria_code/brokers/planning.py +296 -0
  227. aria_code/brokers/registry.py +213 -0
  228. aria_code/brokers/trading.py +267 -0
  229. aria_code/canva_client.py +4 -0
  230. aria_code/change_store.py +127 -0
  231. aria_code/clients/__init__.py +0 -0
  232. aria_code/clients/aliyun_data_client.py +660 -0
  233. aria_code/clients/aria_relay_client.py +182 -0
  234. aria_code/clients/canva_client.py +284 -0
  235. aria_code/clients/figma_client.py +100 -0
  236. aria_code/clients/football_data_client.py +1671 -0
  237. aria_code/clients/kling_video_client.py +182 -0
  238. aria_code/clients/market_data_client.py +1976 -0
  239. aria_code/clients/openai_image_client.py +242 -0
  240. aria_code/clients/runway_video_client.py +157 -0
  241. aria_code/clients/warehouse_erp_client.py +105 -0
  242. aria_code/cloud_memory.py +181 -0
  243. aria_code/command_safety.py +19 -0
  244. aria_code/computer_use_tools.py +4 -0
  245. aria_code/dashboard_generator.py +579 -0
  246. aria_code/data_analysis_tools.py +4 -0
  247. aria_code/data_cleaner.py +483 -0
  248. aria_code/data_service.py +513 -0
  249. aria_code/datasources/__init__.py +23 -0
  250. aria_code/datasources/base.py +166 -0
  251. aria_code/datasources/router.py +253 -0
  252. aria_code/datasources/sources/__init__.py +15 -0
  253. aria_code/datasources/sources/akshare_source.py +269 -0
  254. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  255. aria_code/datasources/sources/edgar_source.py +219 -0
  256. aria_code/datasources/sources/finnhub_source.py +198 -0
  257. aria_code/datasources/sources/fred_source.py +223 -0
  258. aria_code/datasources/sources/tushare_source.py +143 -0
  259. aria_code/datasources/sources/web_scraper_source.py +278 -0
  260. aria_code/datasources/sources/world_bank_source.py +206 -0
  261. aria_code/datasources/sources/yfinance_source.py +152 -0
  262. aria_code/demo_player.py +207 -0
  263. aria_code/doctor.py +813 -0
  264. aria_code/domain/__init__.py +0 -0
  265. aria_code/domain/backtest_engine.py +354 -0
  266. aria_code/domain/backtest_report.py +472 -0
  267. aria_code/domain/finance_formulas.py +389 -0
  268. aria_code/domain/portfolio_ledger.py +307 -0
  269. aria_code/domain/strategy_vault.py +421 -0
  270. aria_code/evals/__init__.py +37 -0
  271. aria_code/evals/harness.py +598 -0
  272. aria_code/evals/runner.py +238 -0
  273. aria_code/external_agent_runner.py +115 -0
  274. aria_code/figma_client.py +4 -0
  275. aria_code/file_analysis_tools.py +4 -0
  276. aria_code/finance_formulas.py +4 -0
  277. aria_code/football_data_client.py +4 -0
  278. aria_code/image_gen_tools.py +4 -0
  279. aria_code/intent_classifier.py +386 -0
  280. aria_code/kling_video_client.py +4 -0
  281. aria_code/licensing.py +151 -0
  282. aria_code/local_finance_tools.py +4 -0
  283. aria_code/local_image_provider.py +4 -0
  284. aria_code/local_llm_provider.py +4 -0
  285. aria_code/macro_tools.py +4 -0
  286. aria_code/markdown_pdf.py +289 -0
  287. aria_code/market_data_client.py +4 -0
  288. aria_code/mcp_client.py +639 -0
  289. aria_code/memory_manager.py +246 -0
  290. aria_code/model_capability.py +486 -0
  291. aria_code/notification_tools.py +4 -0
  292. aria_code/openai_image_client.py +4 -0
  293. aria_code/packages/__init__.py +23 -0
  294. aria_code/packages/adk_bridge/__init__.py +11 -0
  295. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  296. aria_code/packages/adk_bridge/market_tools.py +125 -0
  297. aria_code/packages/aria_agents/__init__.py +5 -0
  298. aria_code/packages/aria_agents/manifest.py +78 -0
  299. aria_code/packages/aria_core/__init__.py +34 -0
  300. aria_code/packages/aria_core/architecture.py +269 -0
  301. aria_code/packages/aria_core/export.py +147 -0
  302. aria_code/packages/aria_core/manifest.py +65 -0
  303. aria_code/packages/aria_core/paths.py +57 -0
  304. aria_code/packages/aria_core/secure_file.py +68 -0
  305. aria_code/packages/aria_infra/__init__.py +15 -0
  306. aria_code/packages/aria_infra/arthera.py +52 -0
  307. aria_code/packages/aria_infra/doctor.py +246 -0
  308. aria_code/packages/aria_infra/product.py +40 -0
  309. aria_code/packages/aria_mcp/__init__.py +25 -0
  310. aria_code/packages/aria_mcp/bridge.py +201 -0
  311. aria_code/packages/aria_mcp/circuit.py +82 -0
  312. aria_code/packages/aria_mcp/config.py +111 -0
  313. aria_code/packages/aria_mcp/installer.py +95 -0
  314. aria_code/packages/aria_mcp/server.py +1550 -0
  315. aria_code/packages/aria_mcp/tools.py +81 -0
  316. aria_code/packages/aria_sdk/__init__.py +19 -0
  317. aria_code/packages/aria_sdk/client.py +396 -0
  318. aria_code/packages/aria_sdk/providers.py +70 -0
  319. aria_code/packages/aria_sdk/streaming.py +79 -0
  320. aria_code/packages/aria_sdk/types.py +86 -0
  321. aria_code/packages/aria_services/__init__.py +92 -0
  322. aria_code/packages/aria_services/cache.py +60 -0
  323. aria_code/packages/aria_services/context.py +356 -0
  324. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  325. aria_code/packages/aria_services/data.py +11 -0
  326. aria_code/packages/aria_services/provider_health.py +268 -0
  327. aria_code/packages/aria_services/references.py +247 -0
  328. aria_code/packages/aria_services/registry.py +249 -0
  329. aria_code/packages/aria_services/research_protocol.py +57 -0
  330. aria_code/packages/aria_services/research_quality.py +186 -0
  331. aria_code/packages/aria_services/settings.py +157 -0
  332. aria_code/packages/aria_services/usage.py +151 -0
  333. aria_code/packages/aria_skills/__init__.py +46 -0
  334. aria_code/packages/aria_skills/catalog.py +149 -0
  335. aria_code/packages/aria_skills/loader.py +455 -0
  336. aria_code/packages/aria_skills/registry.py +84 -0
  337. aria_code/packages/aria_tools/__init__.py +5 -0
  338. aria_code/packages/aria_tools/financial/__init__.py +8 -0
  339. aria_code/packages/aria_tools/financial/_paths.py +130 -0
  340. aria_code/packages/aria_tools/financial/compliance.py +31 -0
  341. aria_code/packages/aria_tools/financial/factors.py +33 -0
  342. aria_code/packages/aria_tools/financial/risk_tools.py +33 -0
  343. aria_code/packages/aria_tools/financial/strategy.py +31 -0
  344. aria_code/packages/aria_tools/registry.py +128 -0
  345. aria_code/packages/quant_engine/__init__.py +34 -0
  346. aria_code/packages/quant_engine/_namespace.py +62 -0
  347. aria_code/packages/quant_engine/backtest/__init__.py +14 -0
  348. aria_code/packages/quant_engine/backtest/core.py +53 -0
  349. aria_code/packages/quant_engine/backtest/engine.py +96 -0
  350. aria_code/packages/quant_engine/portfolio/__init__.py +13 -0
  351. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  352. aria_code/packages/quant_engine/sports/__init__.py +80 -0
  353. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  354. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  355. aria_code/packages/quant_engine/sports/elo.py +300 -0
  356. aria_code/packages/quant_engine/sports/form.py +188 -0
  357. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  358. aria_code/packages/quant_engine/sports/ml_model.py +377 -0
  359. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  360. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  361. aria_code/packages/quant_engine/stochastic/__init__.py +35 -0
  362. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  363. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  364. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  365. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  366. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  367. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  368. aria_code/packs/__init__.py +85 -0
  369. aria_code/packs/base.py +168 -0
  370. aria_code/packs/finance/__init__.py +250 -0
  371. aria_code/packs/logistics/__init__.py +169 -0
  372. aria_code/packs/payments/__init__.py +139 -0
  373. aria_code/packs/realty/__init__.py +190 -0
  374. aria_code/packs/registry.py +202 -0
  375. aria_code/packs/rules.py +90 -0
  376. aria_code/plan_utils.py +194 -0
  377. aria_code/plugin_loader.py +328 -0
  378. aria_code/portfolio_ledger.py +4 -0
  379. aria_code/preview_server.py +449 -0
  380. aria_code/privacy/__init__.py +5 -0
  381. aria_code/privacy/feedback.py +123 -0
  382. aria_code/project_review.py +278 -0
  383. aria_code/project_review_api.py +235 -0
  384. aria_code/project_review_server.py +18 -0
  385. aria_code/project_tools.py +4 -0
  386. aria_code/providers/__init__.py +30 -0
  387. aria_code/providers/llm/__init__.py +19 -0
  388. aria_code/providers/llm/anthropic.py +184 -0
  389. aria_code/providers/llm/autoconfig.py +213 -0
  390. aria_code/providers/llm/base.py +139 -0
  391. aria_code/providers/llm/custom.py +158 -0
  392. aria_code/providers/llm/ollama.py +129 -0
  393. aria_code/providers/llm/openai_compat.py +313 -0
  394. aria_code/providers/llm/registry.py +444 -0
  395. aria_code/providers/local_image_provider.py +202 -0
  396. aria_code/providers/local_llm_provider.py +589 -0
  397. aria_code/realty_data_tools.py +4 -0
  398. aria_code/report_exporters.py +150 -0
  399. aria_code/report_generator.py +1764 -0
  400. aria_code/runtime/__init__.py +169 -0
  401. aria_code/runtime/acceptance.py +481 -0
  402. aria_code/runtime/agent_loop.py +1520 -0
  403. aria_code/runtime/approval.py +57 -0
  404. aria_code/runtime/budget.py +232 -0
  405. aria_code/runtime/checkpoints.py +432 -0
  406. aria_code/runtime/crash_report.py +174 -0
  407. aria_code/runtime/events.py +113 -0
  408. aria_code/runtime/gateway.py +150 -0
  409. aria_code/runtime/lsp.py +484 -0
  410. aria_code/runtime/orchestrator.py +55 -0
  411. aria_code/runtime/repo_map.py +700 -0
  412. aria_code/runtime/run_state.py +127 -0
  413. aria_code/runtime/run_store.py +450 -0
  414. aria_code/runtime/self_healing.py +422 -0
  415. aria_code/runtime/subagent.py +547 -0
  416. aria_code/runtime/task_ledger.py +86 -0
  417. aria_code/runtime/task_orchestrator.py +90 -0
  418. aria_code/runtime/tool_executor.py +217 -0
  419. aria_code/runtime/tool_policy.py +107 -0
  420. aria_code/runtime/worktrees.py +215 -0
  421. aria_code/runway_video_client.py +4 -0
  422. aria_code/safety/__init__.py +23 -0
  423. aria_code/safety/permissions.py +306 -0
  424. aria_code/safety/service.py +102 -0
  425. aria_code/setup_wizard.py +673 -0
  426. aria_code/spreadsheet_tools.py +4 -0
  427. aria_code/strategy_vault.py +4 -0
  428. aria_code/tools/__init__.py +0 -0
  429. aria_code/tools/artifact_tools.py +252 -0
  430. aria_code/tools/broker_tools.py +92 -0
  431. aria_code/tools/build_quant_engine.py +97 -0
  432. aria_code/tools/code_audit_tools.py +192 -0
  433. aria_code/tools/computer_use_tools.py +504 -0
  434. aria_code/tools/data_analysis_tools.py +809 -0
  435. aria_code/tools/enterprise_finance_tools.py +156 -0
  436. aria_code/tools/extended_tools.py +240 -0
  437. aria_code/tools/file_analysis_tools.py +885 -0
  438. aria_code/tools/image_gen_tools.py +114 -0
  439. aria_code/tools/local_finance_tools.py +3417 -0
  440. aria_code/tools/logistics_tools.py +135 -0
  441. aria_code/tools/macro_tools.py +369 -0
  442. aria_code/tools/notification_tools.py +249 -0
  443. aria_code/tools/project_tools.py +525 -0
  444. aria_code/tools/realty_data_tools.py +659 -0
  445. aria_code/tools/spreadsheet_tools.py +388 -0
  446. aria_code/tools/stripe_tools.py +115 -0
  447. aria_code/ui/__init__.py +105 -0
  448. aria_code/ui/banner.py +508 -0
  449. aria_code/ui/completer.py +501 -0
  450. aria_code/ui/console.py +281 -0
  451. aria_code/ui/image_render.py +243 -0
  452. aria_code/ui/input_box.py +488 -0
  453. aria_code/ui/picker.py +201 -0
  454. aria_code/ui/render/__init__.py +11 -0
  455. aria_code/ui/render/finance.py +1705 -0
  456. aria_code/ui/render/market.py +225 -0
  457. aria_code/ui/render/output.py +775 -0
  458. aria_code/ui/render/responsive.py +75 -0
  459. aria_code/ui/render/team.py +354 -0
  460. aria_code/ui/robot.py +235 -0
  461. aria_code/ui/startup_dashboard.py +109 -0
  462. aria_code/utils/vscode_uri.py +37 -0
  463. aria_code/video_analysis.py +147 -0
  464. aria_code/video_editor.py +271 -0
  465. aria_code/workspace/__init__.py +6 -0
  466. aria_code/workspace/files.py +193 -0
  467. aria_code/workspace/verify.py +113 -0
  468. aria_code/workspace_changes.py +199 -0
  469. aria_code/workspace_context.py +233 -0
  470. aria_code/workspace_service.py +196 -0
  471. aria_code/workspace_tools.py +150 -0
  472. aria_code-0.52.0.dist-info/METADATA +237 -0
  473. aria_code-0.52.0.dist-info/RECORD +478 -0
  474. aria_code-0.52.0.dist-info/WHEEL +5 -0
  475. aria_code-0.52.0.dist-info/entry_points.txt +3 -0
  476. aria_code-0.52.0.dist-info/licenses/LICENSE +201 -0
  477. aria_code-0.52.0.dist-info/licenses/NOTICE +49 -0
  478. 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
+ }