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,109 @@
1
+ """State model and responsive layout policy for the startup dashboard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import re
7
+ from typing import Literal, Optional
8
+
9
+
10
+ DashboardLayout = Literal["wide", "stacked", "minimal"]
11
+
12
+
13
+ def select_dashboard_layout(width: int, height: Optional[int] = None) -> DashboardLayout:
14
+ """Choose a stable layout for the available terminal width."""
15
+ if width >= 76:
16
+ return "wide"
17
+ if height is not None and height <= 24:
18
+ return "minimal"
19
+ if width >= 64:
20
+ return "stacked"
21
+ return "minimal"
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class StartupDashboardViewModel:
26
+ """Display-ready startup state, independent from Rich and the CLI runtime."""
27
+
28
+ version: str
29
+ runtime_label: str
30
+ cwd: str
31
+ control_status: str
32
+ health_status: str
33
+ tool_count: int
34
+ skill_count: int
35
+ lang: str = "en"
36
+ first_run: bool = False
37
+ update_notice: Optional[str] = None
38
+ auto_healed_from: str = ""
39
+ current_id: str = ""
40
+ badge: str = ""
41
+ best_lite_id: str = ""
42
+ best_lite_installed: bool = True
43
+ git_branch: str = ""
44
+ git_dirty: bool = False
45
+ mcp_server_count: int = 0
46
+
47
+ @property
48
+ def is_zh(self) -> bool:
49
+ return self.lang.lower().startswith("zh")
50
+
51
+ @property
52
+ def capabilities(self) -> str:
53
+ parts = []
54
+ if self.mcp_server_count:
55
+ parts.append(
56
+ f"MCP {self.mcp_server_count}"
57
+ if not self.is_zh
58
+ else f"MCP {self.mcp_server_count}"
59
+ )
60
+ if self.is_zh:
61
+ parts.extend((f"{self.tool_count} 个工具", f"{self.skill_count} 个技能"))
62
+ else:
63
+ tool_word = "tool" if self.tool_count == 1 else "tools"
64
+ skill_word = "skill" if self.skill_count == 1 else "skills"
65
+ parts.extend((f"{self.tool_count} {tool_word}", f"{self.skill_count} {skill_word}"))
66
+ return " · ".join(parts)
67
+
68
+ @property
69
+ def workspace_state(self) -> str:
70
+ if not self.git_branch:
71
+ return ""
72
+ state = "有改动" if self.is_zh else "dirty"
73
+ clean = "干净" if self.is_zh else "clean"
74
+ return f"{self.git_branch} · {state if self.git_dirty else clean}"
75
+
76
+ @property
77
+ def compact_health(self) -> str:
78
+ plain = re.sub(r"\[/?[^]]+\]", "", self.health_status)
79
+ match = re.search(r"Ollama\s+(?:online|在线)\s*·\s*(\d+)", plain, re.IGNORECASE)
80
+ if match:
81
+ prefix = "本地" if self.is_zh else "Local"
82
+ return f"{prefix}: Ollama {match.group(1)}"
83
+ if "offline" in plain.lower() or "离线" in plain:
84
+ return "本地: Ollama 离线" if self.is_zh else "Local: Ollama offline"
85
+ return plain
86
+
87
+ @property
88
+ def getting_started_title(self) -> str:
89
+ return "快速开始" if self.is_zh else "Quick start"
90
+
91
+ @property
92
+ def runtime_title(self) -> str:
93
+ return "运行状态" if self.is_zh else "Runtime"
94
+
95
+ @property
96
+ def whats_new_title(self) -> str:
97
+ return "版本更新" if self.is_zh else "What's new"
98
+
99
+ @property
100
+ def getting_started_lines(self) -> tuple[str, str]:
101
+ if self.is_zh:
102
+ return (
103
+ "直接描述任务即可",
104
+ "/plan · /help · @ 上下文",
105
+ )
106
+ return (
107
+ "Describe the task naturally",
108
+ "/plan · /help · @ context",
109
+ )
@@ -0,0 +1,37 @@
1
+ """
2
+ src/aria_code/utils/vscode_uri.py — VS Code / Cursor / Windsurf URI Protocol Generator
3
+ =====================================================================================
4
+ Generates standard deep links to open files directly in local IDEs.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+
14
+ def get_ide_uri(
15
+ file_path: str,
16
+ line: int = 1,
17
+ column: int = 1,
18
+ editor: str = "vscode",
19
+ ) -> str:
20
+ """
21
+ Generate deep link URI for opening a local file in VS Code, Cursor, or Windsurf.
22
+
23
+ Supported editors:
24
+ - "vscode" (default) -> vscode://file/{path}:{line}:{column}
25
+ - "cursor" -> cursor://file/{path}:{line}:{column}
26
+ - "windsurf" -> windsurf://file/{path}:{line}:{column}
27
+ - "vscodium" -> vscodium://file/{path}:{line}:{column}
28
+ """
29
+ abs_path = os.path.abspath(os.path.expanduser(file_path))
30
+ scheme_map = {
31
+ "vscode": "vscode",
32
+ "cursor": "cursor",
33
+ "windsurf": "windsurf",
34
+ "vscodium": "vscodium",
35
+ }
36
+ scheme = scheme_map.get(editor.lower(), "vscode")
37
+ return f"{scheme}://file/{abs_path}:{line}:{column}"
@@ -0,0 +1,147 @@
1
+ """Layer 2 of the video architecture: local, free AI-assisted analysis —
2
+ produces editing *decisions/suggestions* (transcript, scene-cut timestamps),
3
+ never new pixels. Both pieces run entirely on this machine:
4
+
5
+ transcribe_video() — faster-whisper (CTranslate2-backed, much lighter
6
+ than diffusion models: the "base" model is ~150MB, "small" ~500MB)
7
+ detect_scenes() — opencv (already an existing optional dependency,
8
+ used elsewhere for video keyframe extraction) via a histogram-diff
9
+ cut detector — no new heavy model, just frame comparison
10
+
11
+ Both are lazy-imported and optional, same pattern as local_image_provider.py.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import shutil
17
+ import subprocess
18
+ import tempfile
19
+ from pathlib import Path
20
+ from typing import Any, Dict, List, Optional
21
+
22
+ _whisper_model_cache: Dict[str, Any] = {}
23
+
24
+
25
+ def _require_ffmpeg() -> Optional[str]:
26
+ if shutil.which("ffmpeg") is None:
27
+ return "ffmpeg not found on PATH. Install it: brew install ffmpeg"
28
+ return None
29
+
30
+
31
+ def _extract_audio(input_path: str, out_wav: str) -> Dict[str, Any]:
32
+ missing = _require_ffmpeg()
33
+ if missing:
34
+ return {"success": False, "error": missing}
35
+ try:
36
+ result = subprocess.run(
37
+ ["ffmpeg", "-y", "-i", input_path, "-vn", "-ac", "1", "-ar", "16000", out_wav],
38
+ capture_output=True, text=True, timeout=120,
39
+ )
40
+ except Exception as exc:
41
+ return {"success": False, "error": str(exc)}
42
+ if result.returncode != 0:
43
+ return {"success": False, "error": result.stderr[-500:]}
44
+ return {"success": True}
45
+
46
+
47
+ def _get_whisper_model(model_size: str):
48
+ if model_size in _whisper_model_cache:
49
+ return _whisper_model_cache[model_size]
50
+ try:
51
+ from faster_whisper import WhisperModel
52
+ except ImportError as exc:
53
+ raise RuntimeError(
54
+ "Transcription needs the optional 'video_analysis' extra: "
55
+ "pip install -e '.[video_analysis]' (adds faster-whisper). "
56
+ f"Missing: {exc}"
57
+ ) from exc
58
+ model = WhisperModel(model_size, device="cpu", compute_type="int8")
59
+ _whisper_model_cache[model_size] = model
60
+ return model
61
+
62
+
63
+ def transcribe_video(input_path: str, *, model_size: str = "base", language: Optional[str] = None) -> Dict[str, Any]:
64
+ """Transcribe a video's speech track locally via faster-whisper.
65
+
66
+ Returns segments with timestamps (for captions/subtitles) plus the full
67
+ text. Blocking, CPU-bound — callers on an event loop must run this in an
68
+ executor, same as every other client in this codebase.
69
+ """
70
+ if not Path(input_path).exists():
71
+ return {"success": False, "error": f"File not found: {input_path}"}
72
+
73
+ with tempfile.TemporaryDirectory() as tmpdir:
74
+ wav_path = str(Path(tmpdir) / "audio.wav")
75
+ extracted = _extract_audio(input_path, wav_path)
76
+ if not extracted["success"]:
77
+ return extracted
78
+
79
+ try:
80
+ model = _get_whisper_model(model_size)
81
+ segments, info = model.transcribe(wav_path, language=language)
82
+ segment_list = [
83
+ {"start": round(s.start, 2), "end": round(s.end, 2), "text": s.text.strip()}
84
+ for s in segments
85
+ ]
86
+ except Exception as exc:
87
+ return {"success": False, "error": str(exc)}
88
+
89
+ full_text = " ".join(s["text"] for s in segment_list)
90
+ return {
91
+ "success": True,
92
+ "language": getattr(info, "language", language),
93
+ "segments": segment_list,
94
+ "text": full_text,
95
+ }
96
+
97
+
98
+ def detect_scenes(input_path: str, *, threshold: float = 30.0, sample_every_n_frames: int = 5) -> Dict[str, Any]:
99
+ """Detect scene-cut timestamps via consecutive-frame histogram
100
+ difference — sampling every `sample_every_n_frames` rather than every
101
+ frame keeps this fast on longer videos at the cost of cut-timing
102
+ precision within that sampling window.
103
+
104
+ `threshold` is the Bhattacharyya-distance-derived diff score above
105
+ which two sampled frames are considered different scenes; higher =
106
+ less sensitive (fewer, more confident cuts).
107
+ """
108
+ if not Path(input_path).exists():
109
+ return {"success": False, "error": f"File not found: {input_path}"}
110
+ try:
111
+ import cv2
112
+ except ImportError as exc:
113
+ return {
114
+ "success": False,
115
+ "error": (
116
+ "Scene detection needs the optional 'video' extra: "
117
+ f"pip install -e '.[video]' (adds opencv-python). Missing: {exc}"
118
+ ),
119
+ }
120
+
121
+ cap = cv2.VideoCapture(input_path)
122
+ if not cap.isOpened():
123
+ return {"success": False, "error": f"Could not open video: {input_path}"}
124
+
125
+ fps = cap.get(cv2.CAP_PROP_FPS) or 25.0
126
+ cuts: List[float] = []
127
+ prev_hist = None
128
+ frame_idx = 0
129
+
130
+ try:
131
+ while True:
132
+ ok, frame = cap.read()
133
+ if not ok:
134
+ break
135
+ if frame_idx % sample_every_n_frames == 0:
136
+ hist = cv2.calcHist([frame], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
137
+ cv2.normalize(hist, hist)
138
+ if prev_hist is not None:
139
+ diff = cv2.compareHist(prev_hist, hist, cv2.HISTCMP_BHATTACHARYYA) * 100
140
+ if diff > threshold:
141
+ cuts.append(round(frame_idx / fps, 2))
142
+ prev_hist = hist
143
+ frame_idx += 1
144
+ finally:
145
+ cap.release()
146
+
147
+ return {"success": True, "fps": fps, "total_frames": frame_idx, "scene_cuts_sec": cuts}
@@ -0,0 +1,271 @@
1
+ """Layer 1 of the video architecture: deterministic ffmpeg-based editing —
2
+ trim, concat, overlay text, overlay audio, convert format/aspect, change
3
+ speed. No AI, no model weights, no API key — just a subprocess wrapper
4
+ around the `ffmpeg`/`ffprobe` CLIs, same pattern as project_tools.py's
5
+ `_git()` helper (timeout-bounded, capture output, never raise — return an
6
+ error dict instead).
7
+
8
+ Layer 2 (video_analysis.py, local Whisper transcription + opencv scene
9
+ detection) and Layer 3 (cloud AI generation, not built yet — needs a
10
+ provider/budget decision first) are separate modules.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import shutil
16
+ import subprocess
17
+ import tempfile
18
+ from pathlib import Path
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ DEFAULT_TIMEOUT = 300.0
22
+
23
+
24
+ def _require_ffmpeg() -> Optional[str]:
25
+ if shutil.which("ffmpeg") is None:
26
+ return "ffmpeg not found on PATH. Install it: brew install ffmpeg"
27
+ return None
28
+
29
+
30
+ def _run_ffmpeg(args: List[str], *, timeout: float = DEFAULT_TIMEOUT) -> Dict[str, Any]:
31
+ """Run an ffmpeg command. Returns {"success": bool, "error"?: str}."""
32
+ missing = _require_ffmpeg()
33
+ if missing:
34
+ return {"success": False, "error": missing}
35
+ try:
36
+ result = subprocess.run(
37
+ ["ffmpeg", "-y", *args],
38
+ capture_output=True, text=True, timeout=timeout,
39
+ )
40
+ except subprocess.TimeoutExpired:
41
+ return {"success": False, "error": f"ffmpeg timed out after {timeout:.0f}s"}
42
+ except Exception as exc:
43
+ return {"success": False, "error": str(exc)}
44
+ if result.returncode != 0:
45
+ return {"success": False, "error": result.stderr[-800:]}
46
+ return {"success": True}
47
+
48
+
49
+ def _default_output(input_path: str, suffix: str, ext: Optional[str] = None) -> Path:
50
+ src = Path(input_path)
51
+ from artifacts import create_user_artifact
52
+
53
+ artifact = create_user_artifact("video", src.stem, f"{src.stem}_{suffix}", ext or src.suffix)
54
+ return artifact.path
55
+
56
+
57
+ def probe_video(input_path: str) -> Dict[str, Any]:
58
+ """Return duration/resolution/codec info via ffprobe."""
59
+ if shutil.which("ffprobe") is None:
60
+ return {"success": False, "error": "ffprobe not found on PATH. Install it: brew install ffmpeg"}
61
+ if not Path(input_path).exists():
62
+ return {"success": False, "error": f"File not found: {input_path}"}
63
+ try:
64
+ result = subprocess.run(
65
+ ["ffprobe", "-v", "error", "-show_entries",
66
+ "stream=width,height,codec_type,codec_name,r_frame_rate:format=duration,size",
67
+ "-of", "json", input_path],
68
+ capture_output=True, text=True, timeout=30,
69
+ )
70
+ except Exception as exc:
71
+ return {"success": False, "error": str(exc)}
72
+ if result.returncode != 0:
73
+ return {"success": False, "error": result.stderr[-500:]}
74
+ import json
75
+ return {"success": True, **json.loads(result.stdout)}
76
+
77
+
78
+ def trim_video(input_path: str, start: float, end: float, *, output_path: Optional[str] = None) -> Dict[str, Any]:
79
+ """Cut [start, end] seconds out of a video (re-encodes for frame-accurate cuts)."""
80
+ if not Path(input_path).exists():
81
+ return {"success": False, "error": f"File not found: {input_path}"}
82
+ if end <= start:
83
+ return {"success": False, "error": "end must be greater than start"}
84
+ out = Path(output_path) if output_path else _default_output(input_path, "trimmed")
85
+ result = _run_ffmpeg([
86
+ "-ss", str(start), "-to", str(end), "-i", input_path,
87
+ "-c:v", "libx264", "-c:a", "aac", str(out),
88
+ ])
89
+ if not result["success"]:
90
+ return result
91
+ return {"success": True, "path": str(out)}
92
+
93
+
94
+ def concat_videos(input_paths: List[str], *, output_path: Optional[str] = None) -> Dict[str, Any]:
95
+ """Concatenate multiple videos in order. Re-encodes each to a common
96
+ codec first so mismatched source formats (different codec/resolution)
97
+ don't silently produce a broken concat — the naive ffmpeg concat demuxer
98
+ only works when all inputs already share a codec."""
99
+ for p in input_paths:
100
+ if not Path(p).exists():
101
+ return {"success": False, "error": f"File not found: {p}"}
102
+ if len(input_paths) < 2:
103
+ return {"success": False, "error": "Need at least 2 videos to concatenate"}
104
+
105
+ out = Path(output_path) if output_path else _default_output(input_paths[0], "concat")
106
+ filter_inputs = "".join(f"[{i}:v:0][{i}:a:0]" for i in range(len(input_paths)))
107
+ args = []
108
+ for p in input_paths:
109
+ args += ["-i", p]
110
+ args += [
111
+ "-filter_complex", f"{filter_inputs}concat=n={len(input_paths)}:v=1:a=1[outv][outa]",
112
+ "-map", "[outv]", "-map", "[outa]",
113
+ "-c:v", "libx264", "-c:a", "aac", str(out),
114
+ ]
115
+ result = _run_ffmpeg(args)
116
+ if not result["success"]:
117
+ return result
118
+ return {"success": True, "path": str(out)}
119
+
120
+
121
+ def overlay_text(
122
+ input_path: str,
123
+ text: str,
124
+ *,
125
+ position: str = "bottom",
126
+ font_size: int = 36,
127
+ font_color: str = "white",
128
+ output_path: Optional[str] = None,
129
+ ) -> Dict[str, Any]:
130
+ """Burn a text overlay onto the video.
131
+
132
+ Renders the text as a transparent PNG via Pillow and composites it with
133
+ ffmpeg's `overlay` filter, rather than ffmpeg's own `drawtext` filter —
134
+ `drawtext` needs ffmpeg built with libfreetype, which isn't guaranteed
135
+ (confirmed absent on a stock Homebrew ffmpeg install: `No such filter:
136
+ 'drawtext'`). Rendering the text ourselves works on any ffmpeg build.
137
+ """
138
+ if not Path(input_path).exists():
139
+ return {"success": False, "error": f"File not found: {input_path}"}
140
+
141
+ probe = probe_video(input_path)
142
+ if not probe["success"]:
143
+ return probe
144
+ video_stream = next((s for s in probe.get("streams", []) if s.get("codec_type") == "video"), None)
145
+ if not video_stream:
146
+ return {"success": False, "error": "Could not determine video dimensions"}
147
+ width, height = int(video_stream["width"]), int(video_stream["height"])
148
+
149
+ from PIL import Image, ImageDraw, ImageFont
150
+
151
+ overlay_img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
152
+ draw = ImageDraw.Draw(overlay_img)
153
+ try:
154
+ font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
155
+ except Exception:
156
+ font = ImageFont.load_default()
157
+
158
+ bbox = draw.textbbox((0, 0), text, font=font)
159
+ text_w, text_h = bbox[2] - bbox[0], bbox[3] - bbox[1]
160
+ margin = 40
161
+ positions = {
162
+ "top": ((width - text_w) // 2, margin),
163
+ "bottom": ((width - text_w) // 2, height - text_h - margin),
164
+ "center": ((width - text_w) // 2, (height - text_h) // 2),
165
+ }
166
+ x, y = positions.get(position, positions["bottom"])
167
+ pad = 10
168
+ draw.rectangle([x - pad, y - pad, x + text_w + pad, y + text_h + pad], fill=(0, 0, 0, 128))
169
+ draw.text((x, y), text, font=font, fill=font_color)
170
+
171
+ out = Path(output_path) if output_path else _default_output(input_path, "text")
172
+ with tempfile.TemporaryDirectory() as tmpdir:
173
+ overlay_path = str(Path(tmpdir) / "overlay.png")
174
+ overlay_img.save(overlay_path)
175
+ result = _run_ffmpeg([
176
+ "-i", input_path, "-i", overlay_path,
177
+ "-filter_complex", "[0:v][1:v]overlay=0:0",
178
+ "-c:a", "copy", str(out),
179
+ ])
180
+ if not result["success"]:
181
+ return result
182
+ return {"success": True, "path": str(out)}
183
+
184
+
185
+ def overlay_audio(input_path: str, audio_path: str, *, replace: bool = False, output_path: Optional[str] = None) -> Dict[str, Any]:
186
+ """Add or replace the audio track. replace=True drops the original audio
187
+ entirely; replace=False mixes the new track under the original."""
188
+ if not Path(input_path).exists():
189
+ return {"success": False, "error": f"File not found: {input_path}"}
190
+ if not Path(audio_path).exists():
191
+ return {"success": False, "error": f"File not found: {audio_path}"}
192
+
193
+ out = Path(output_path) if output_path else _default_output(input_path, "audio")
194
+ if replace:
195
+ args = ["-i", input_path, "-i", audio_path, "-map", "0:v:0", "-map", "1:a:0",
196
+ "-c:v", "copy", "-shortest", str(out)]
197
+ else:
198
+ args = ["-i", input_path, "-i", audio_path,
199
+ "-filter_complex", "[0:a][1:a]amix=inputs=2:duration=first[a]",
200
+ "-map", "0:v:0", "-map", "[a]", "-c:v", "copy", "-shortest", str(out)]
201
+ result = _run_ffmpeg(args)
202
+ if not result["success"]:
203
+ return result
204
+ return {"success": True, "path": str(out)}
205
+
206
+
207
+ def convert_video(
208
+ input_path: str,
209
+ *,
210
+ output_format: Optional[str] = None,
211
+ aspect: Optional[str] = None,
212
+ output_path: Optional[str] = None,
213
+ ) -> Dict[str, Any]:
214
+ """Convert container/codec format and/or reframe to a target aspect
215
+ ratio (e.g. "9:16" for vertical social, "1:1" for square) via
216
+ crop-to-fill — center-crops rather than letterboxing, since a cropped
217
+ fill is what "reframe for a platform" almost always means in practice."""
218
+ if not Path(input_path).exists():
219
+ return {"success": False, "error": f"File not found: {input_path}"}
220
+
221
+ ext = f".{output_format}" if output_format else None
222
+ out = Path(output_path) if output_path else _default_output(input_path, "converted", ext)
223
+
224
+ vf = None
225
+ if aspect:
226
+ try:
227
+ aw, ah = (int(x) for x in aspect.split(":"))
228
+ except ValueError:
229
+ return {"success": False, "error": "aspect must be like '9:16'"}
230
+ vf = f"crop='min(iw,ih*{aw}/{ah})':'min(ih,iw*{ah}/{aw})',scale=trunc(iw/2)*2:trunc(ih/2)*2"
231
+
232
+ args = ["-i", input_path]
233
+ if vf:
234
+ args += ["-vf", vf]
235
+ args += ["-c:v", "libx264", "-c:a", "aac", str(out)]
236
+ result = _run_ffmpeg(args)
237
+ if not result["success"]:
238
+ return result
239
+ return {"success": True, "path": str(out)}
240
+
241
+
242
+ def change_speed(input_path: str, factor: float, *, output_path: Optional[str] = None) -> Dict[str, Any]:
243
+ """Speed up (factor > 1) or slow down (factor < 1) a video, audio pitch-
244
+ corrected. atempo only accepts 0.5-2.0 per filter pass, so factors
245
+ outside that range are chained across multiple atempo stages."""
246
+ if not Path(input_path).exists():
247
+ return {"success": False, "error": f"File not found: {input_path}"}
248
+ if factor <= 0:
249
+ return {"success": False, "error": "factor must be positive"}
250
+
251
+ out = Path(output_path) if output_path else _default_output(input_path, "speed")
252
+ atempo_stages = []
253
+ remaining = factor
254
+ while remaining > 2.0:
255
+ atempo_stages.append(2.0)
256
+ remaining /= 2.0
257
+ while remaining < 0.5:
258
+ atempo_stages.append(0.5)
259
+ remaining /= 0.5
260
+ atempo_stages.append(remaining)
261
+ atempo_filter = ",".join(f"atempo={s:.4f}" for s in atempo_stages)
262
+
263
+ result = _run_ffmpeg([
264
+ "-i", input_path,
265
+ "-filter_complex", f"[0:v]setpts={1/factor:.6f}*PTS[v];[0:a]{atempo_filter}[a]",
266
+ "-map", "[v]", "-map", "[a]",
267
+ "-c:v", "libx264", "-c:a", "aac", str(out),
268
+ ])
269
+ if not result["success"]:
270
+ return result
271
+ return {"success": True, "path": str(out)}
@@ -0,0 +1,6 @@
1
+ """Workspace primitives for Aria Code."""
2
+
3
+ from .files import WorkspaceFiles, WorkspaceSecurity
4
+ from .verify import VerificationPlan, VerificationPlanner
5
+
6
+ __all__ = ["VerificationPlan", "VerificationPlanner", "WorkspaceFiles", "WorkspaceSecurity"]