aria-code 0.167.0__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aria_bots/__init__.py +7 -0
- aria_bots/channels/README.md +97 -0
- aria_bots/channels/__init__.py +1 -0
- aria_bots/channels/approvals.py +263 -0
- aria_bots/channels/conversation.py +280 -0
- aria_bots/channels/digest.py +186 -0
- aria_bots/channels/registry.py +95 -0
- aria_bots/engine.py +144 -0
- aria_bots/feishu_bot.py +1385 -0
- aria_bots/logistics/__init__.py +1 -0
- aria_bots/logistics/carriers.py +253 -0
- aria_bots/logistics/freight.py +154 -0
- aria_bots/logistics/inventory.py +242 -0
- aria_bots/logistics/mcp_server.py +156 -0
- aria_bots/logistics/records.py +119 -0
- aria_bots/logistics/tenancy.py +152 -0
- aria_bots/market.py +91 -0
- aria_bots/relay_client.py +326 -0
- aria_bots/telegram_bot.py +299 -0
- aria_code/__init__.py +111 -0
- aria_code/_bin/aria-native.exe +0 -0
- aria_code/_bin/aria-runtime.exe +0 -0
- aria_code/_version.py +20 -0
- aria_code/account_memory.py +296 -0
- aria_code/account_session.py +166 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +49 -0
- aria_code/agents/base.py +329 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +315 -0
- aria_code/agents/financial/fundamental.py +178 -0
- aria_code/agents/financial/macro.py +111 -0
- aria_code/agents/financial/news.py +231 -0
- aria_code/agents/financial/risk.py +146 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +277 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +392 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +168 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +500 -0
- aria_code/agents/video/__init__.py +16 -0
- aria_code/agents/video/director.py +376 -0
- aria_code/agents/video/qa_agent.py +143 -0
- aria_code/agents/video/script_doctor.py +112 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/navigation.schema.json +110 -0
- aria_code/agents/warehouse/task_agent.py +172 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +9 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/approvals.py +11 -0
- aria_code/apps/channels/conversation.py +11 -0
- aria_code/apps/channels/digest.py +11 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +11 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/app_server.py +656 -0
- aria_code/apps/cli/auto_update.py +125 -0
- aria_code/apps/cli/bootstrap.py +206 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/chat_turn.py +1327 -0
- aria_code/apps/cli/cloud_models.py +231 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +34 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +378 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +215 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
- aria_code/apps/cli/commands/broker_cmds.py +1236 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +189 -0
- aria_code/apps/cli/commands/core_cmds.py +2748 -0
- aria_code/apps/cli/commands/data_cmds.py +521 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/engineering_cmds.py +399 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +544 -0
- aria_code/apps/cli/commands/market_cmds.py +1419 -0
- aria_code/apps/cli/commands/market_context.py +432 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2374 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
- aria_code/apps/cli/commands/report.py +476 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +723 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
- aria_code/apps/cli/commands/workflow_cmds.py +629 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +158 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/conversation_policy.py +53 -0
- aria_code/apps/cli/deterministic.py +200 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/earnings_view.py +172 -0
- aria_code/apps/cli/endpoint_migration.py +64 -0
- aria_code/apps/cli/exec_events.py +255 -0
- aria_code/apps/cli/file_references.py +155 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +239 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
- aria_code/apps/cli/handlers/market_handlers.py +2570 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/headless.py +352 -0
- aria_code/apps/cli/helpers.py +326 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +310 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +305 -0
- aria_code/apps/cli/intent_shadow.py +72 -0
- aria_code/apps/cli/intent_signals.py +347 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +550 -0
- aria_code/apps/cli/main.py +39 -0
- aria_code/apps/cli/market_hours.py +100 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/mixin_binding.py +32 -0
- aria_code/apps/cli/model_catalog.py +652 -0
- aria_code/apps/cli/model_probe.py +169 -0
- aria_code/apps/cli/model_retirement.py +65 -0
- aria_code/apps/cli/native_branding.py +12 -0
- aria_code/apps/cli/native_bridge.py +118 -0
- aria_code/apps/cli/native_entry.py +58 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +78 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +71 -0
- aria_code/apps/cli/prompts/select.py +193 -0
- aria_code/apps/cli/prompts/system_prompts.py +562 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +775 -0
- aria_code/apps/cli/providers/chat_routing.py +139 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
- aria_code/apps/cli/providers/runtime_bridge.py +586 -0
- aria_code/apps/cli/providers/vertexai_stream.py +592 -0
- aria_code/apps/cli/release_download.py +159 -0
- aria_code/apps/cli/review_runner.py +111 -0
- aria_code/apps/cli/runtime_consumer.py +769 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_refresh.py +123 -0
- aria_code/apps/cli/session_store.py +125 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/task_isolation.py +211 -0
- aria_code/apps/cli/terminal_chrome.py +412 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +932 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +113 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +181 -0
- aria_code/apps/cli/tools/market_tools.py +626 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +656 -0
- aria_code/apps/cli/tools/write_tools.py +961 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/transactions.py +269 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +257 -0
- aria_code/apps/cli/updater.py +373 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1663 -0
- aria_code/apps/cli/web_login.py +136 -0
- aria_code/apps/cli/workflow_runner.py +127 -0
- aria_code/apps/cli/workspace_route.py +121 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +5244 -0
- aria_code/aria_daemon.py +1372 -0
- aria_code/aria_feishu_bot.py +16 -0
- aria_code/aria_mcp_server.py +61 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_telegram_bot.py +11 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +16 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1985 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +190 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_server.py +276 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sec_statements.py +217 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/doctor.py +860 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/_finance_core/__init__.py +5 -0
- aria_code/domain/_finance_core/backtest_engine.py +325 -0
- aria_code/domain/_finance_core/performance_stats.py +101 -0
- aria_code/domain/backtest_engine.py +75 -0
- aria_code/domain/backtest_report.py +488 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/performance_stats.py +6 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/behavior.py +113 -0
- aria_code/evals/gap.py +93 -0
- aria_code/evals/harness.py +949 -0
- aria_code/evals/review_probe.py +132 -0
- aria_code/evals/runner.py +425 -0
- aria_code/evals/scorecard.py +322 -0
- aria_code/evals/summary.py +125 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +459 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +744 -0
- aria_code/memory_manager.py +273 -0
- aria_code/model_capability.py +512 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +26 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +58 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +232 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +162 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1705 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +393 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +75 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +304 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +137 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +160 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/_paths.py +92 -0
- aria_code/packages/aria_tools/registry.py +129 -0
- aria_code/packages/quant_engine/__init__.py +33 -0
- aria_code/packages/quant_engine/_namespace.py +24 -0
- aria_code/packages/quant_engine/backtest/__init__.py +10 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +102 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +76 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/healthcare/__init__.py +40 -0
- aria_code/packs/logistics/__init__.py +171 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/google_video_provider.py +256 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/arthera_gateway.py +90 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +137 -0
- aria_code/providers/llm/openai_compat.py +350 -0
- aria_code/providers/llm/registry.py +457 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +549 -0
- aria_code/providers/ollama_models.py +79 -0
- aria_code/providers/openai_params.py +32 -0
- aria_code/providers/tool_messages.py +100 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/review_service.py +497 -0
- aria_code/runtime/__init__.py +176 -0
- aria_code/runtime/acceptance.py +632 -0
- aria_code/runtime/agent_loop.py +1851 -0
- aria_code/runtime/agent_profiles.py +75 -0
- aria_code/runtime/approval.py +71 -0
- aria_code/runtime/baseline.py +219 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +481 -0
- aria_code/runtime/consult.py +65 -0
- aria_code/runtime/contract.py +282 -0
- aria_code/runtime/delivery.py +432 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +167 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/native_index.py +153 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/processes.py +343 -0
- aria_code/runtime/project_graph.py +614 -0
- aria_code/runtime/repo_map.py +712 -0
- aria_code/runtime/research_bundle.py +105 -0
- aria_code/runtime/review.py +269 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +522 -0
- aria_code/runtime/scoped_runs.py +113 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/semantic_diff.py +171 -0
- aria_code/runtime/subagent.py +721 -0
- aria_code/runtime/symbol_edit.py +117 -0
- aria_code/runtime/task_brief.py +46 -0
- aria_code/runtime/task_context.py +145 -0
- aria_code/runtime/task_ledger.py +108 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/task_outcome.py +33 -0
- aria_code/runtime/task_verification.py +266 -0
- aria_code/runtime/task_worktree.py +448 -0
- aria_code/runtime/tool_executor.py +329 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/transactions.py +216 -0
- aria_code/runtime/windows_job.py +115 -0
- aria_code/runtime/workflows.py +222 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +27 -0
- aria_code/safety/client_isolation.py +178 -0
- aria_code/safety/permissions.py +339 -0
- aria_code/safety/risk.py +699 -0
- aria_code/safety/sandbox.py +148 -0
- aria_code/safety/service.py +102 -0
- aria_code/service_endpoints.py +14 -0
- aria_code/setup_wizard.py +684 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +509 -0
- aria_code/tools/data_analysis_tools.py +831 -0
- aria_code/tools/enterprise_finance_tools.py +202 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +880 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3488 -0
- aria_code/tools/logistics_carriers.py +11 -0
- aria_code/tools/logistics_inventory.py +11 -0
- aria_code/tools/logistics_io.py +11 -0
- aria_code/tools/logistics_tenancy.py +11 -0
- aria_code/tools/logistics_tools.py +11 -0
- aria_code/tools/macro_tools.py +412 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +662 -0
- aria_code/tools/spreadsheet_tools.py +399 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/tools/video_tools.py +500 -0
- aria_code/training/__init__.py +1 -0
- aria_code/training/compare.py +117 -0
- aria_code/training/prelude_commands.py +111 -0
- aria_code/training/prelude_eval.py +390 -0
- aria_code/training/prelude_real.py +183 -0
- aria_code/training/prelude_train.py +248 -0
- aria_code/training/sft_dataset.py +230 -0
- aria_code/training/trajectory_dataset.py +263 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +404 -0
- aria_code/ui/completer.py +571 -0
- aria_code/ui/console.py +285 -0
- aria_code/ui/input_box.py +505 -0
- aria_code/ui/mentions.py +206 -0
- aria_code/ui/picker.py +248 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/actions.py +297 -0
- aria_code/ui/render/finance.py +1732 -0
- aria_code/ui/render/market.py +253 -0
- aria_code/ui/render/output.py +954 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +367 -0
- aria_code/ui/robot.py +239 -0
- aria_code/ui/startup_dashboard.py +110 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +518 -0
- aria_code/video_workflow/__init__.py +8 -0
- aria_code/video_workflow/__main__.py +61 -0
- aria_code/video_workflow/engines/__init__.py +18 -0
- aria_code/video_workflow/engines/base.py +69 -0
- aria_code/video_workflow/engines/custom_model.py +204 -0
- aria_code/video_workflow/engines/google_veo.py +94 -0
- aria_code/video_workflow/engines/local_engine.py +71 -0
- aria_code/video_workflow/engines/registry.py +98 -0
- aria_code/video_workflow/google_models.py +278 -0
- aria_code/video_workflow/music/__init__.py +5 -0
- aria_code/video_workflow/music/foley_service.py +132 -0
- aria_code/video_workflow/music/service.py +212 -0
- aria_code/video_workflow/pipeline.py +620 -0
- aria_code/video_workflow/plan.py +202 -0
- aria_code/video_workflow/session.py +297 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +219 -0
- aria_code/workspace/verify.py +167 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.167.0.dist-info/METADATA +330 -0
- aria_code-0.167.0.dist-info/RECORD +601 -0
- aria_code-0.167.0.dist-info/WHEEL +5 -0
- aria_code-0.167.0.dist-info/entry_points.txt +5 -0
- aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.167.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1543 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WorkspaceCommandsMixin — Workspace commands: packages, file, project, init, setup, memory.
|
|
3
|
+
|
|
4
|
+
Extracted from aria_cli.py. Methods' __globals__ are rebound to aria_cli's namespace
|
|
5
|
+
by _rebind_mixin_globals() called at module load time.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ._ui import print_error, rich_box
|
|
11
|
+
from aria_code.packages.aria_core.paths import aria_home
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import asyncio
|
|
16
|
+
import datetime
|
|
17
|
+
import time
|
|
18
|
+
import shlex
|
|
19
|
+
import sys
|
|
20
|
+
import os
|
|
21
|
+
from typing import Dict, Any, Optional
|
|
22
|
+
|
|
23
|
+
def detect_ollama_models_rich(*args, **kwargs):
|
|
24
|
+
from aria_code.apps.cli.helpers import detect_ollama_models_rich as fn
|
|
25
|
+
return fn(*args, **kwargs)
|
|
26
|
+
def _load_project_context(*args, **kwargs):
|
|
27
|
+
from aria_code.apps.cli.helpers import _load_project_context as fn
|
|
28
|
+
return fn(*args, **kwargs)
|
|
29
|
+
def _get__HAS_MCP():
|
|
30
|
+
from aria_code.apps.cli._optional import HAS_MCP as val
|
|
31
|
+
return val
|
|
32
|
+
def _get_LOCAL_TOOL_SCHEMAS():
|
|
33
|
+
# Owned by apps/cli/tool_registry.py; aria_cli fills it in place.
|
|
34
|
+
from ..tool_registry import LOCAL_TOOL_SCHEMAS as val
|
|
35
|
+
return val
|
|
36
|
+
import pathlib
|
|
37
|
+
def _get_provider_key(*args, **kwargs):
|
|
38
|
+
from aria_code.apps.cli.provider_keys import get_provider_key as fn
|
|
39
|
+
return fn(*args, **kwargs)
|
|
40
|
+
def _get_rich_box():
|
|
41
|
+
# Resolved locally now — see ._ui; no aria_cli round-trip.
|
|
42
|
+
return rich_box
|
|
43
|
+
def _arrow_select(*args, **kwargs):
|
|
44
|
+
# Re-exported by aria_cli; taken from its own module here.
|
|
45
|
+
from aria_code.ui.picker import arrow_select as fn
|
|
46
|
+
return fn(*args, **kwargs)
|
|
47
|
+
def _get___version__():
|
|
48
|
+
from aria_code.aria_cli import __version__ as val
|
|
49
|
+
return val
|
|
50
|
+
def _get_LOCAL_TOOLS():
|
|
51
|
+
# Owned by apps/cli/tool_registry.py; aria_cli fills it in place.
|
|
52
|
+
from ..tool_registry import LOCAL_TOOLS as val
|
|
53
|
+
return val
|
|
54
|
+
def get_model_capability(*args, **kwargs):
|
|
55
|
+
from aria_code.apps.cli._optional import get_model_capability as fn
|
|
56
|
+
return fn(*args, **kwargs)
|
|
57
|
+
def _get_MCP_CONFIG_PATH():
|
|
58
|
+
from aria_code.apps.cli._optional import MCP_CONFIG_PATH as val
|
|
59
|
+
return val
|
|
60
|
+
def _get_SESSIONS_DIR():
|
|
61
|
+
from aria_code.aria_cli import SESSIONS_DIR as val
|
|
62
|
+
return val
|
|
63
|
+
def _get__HAS_MODEL_CAP():
|
|
64
|
+
from aria_code.apps.cli._optional import HAS_MODEL_CAP as val
|
|
65
|
+
return val
|
|
66
|
+
def _strip_markdown_fences(*args, **kwargs):
|
|
67
|
+
from aria_code.aria_cli import _strip_markdown_fences as fn
|
|
68
|
+
return fn(*args, **kwargs)
|
|
69
|
+
def _load_data_keys(*args, **kwargs):
|
|
70
|
+
from aria_code.apps.cli.provider_keys import load_data_keys as fn
|
|
71
|
+
return fn(*args, **kwargs)
|
|
72
|
+
|
|
73
|
+
import json
|
|
74
|
+
import asyncio
|
|
75
|
+
import datetime
|
|
76
|
+
import time
|
|
77
|
+
import shlex
|
|
78
|
+
import sys
|
|
79
|
+
import os
|
|
80
|
+
from typing import Dict, Any, Optional
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class WorkspaceCommandsMixin:
|
|
84
|
+
"""Mixin: Workspace commands: packages, file, project, init, setup, memory."""
|
|
85
|
+
|
|
86
|
+
async def cmd_packages(self, args: str):
|
|
87
|
+
"""Show Aria Code package facades and Arthera package bridge status."""
|
|
88
|
+
try:
|
|
89
|
+
from aria_code.packages.aria_agents import list_agent_manifests
|
|
90
|
+
from aria_code.packages.aria_core import (
|
|
91
|
+
build_package_manifest,
|
|
92
|
+
list_architecture_layers,
|
|
93
|
+
required_architecture_layer_names,
|
|
94
|
+
write_package_manifest,
|
|
95
|
+
)
|
|
96
|
+
from aria_code.packages.aria_infra import (
|
|
97
|
+
aria_code_identity,
|
|
98
|
+
build_package_doctor_report,
|
|
99
|
+
discover_arthera_packages,
|
|
100
|
+
)
|
|
101
|
+
from aria_code.packages.aria_mcp import (
|
|
102
|
+
arthera_quant_engine_server_config,
|
|
103
|
+
default_exposures,
|
|
104
|
+
load_mcp_config,
|
|
105
|
+
merge_server_config,
|
|
106
|
+
mcp_server_status,
|
|
107
|
+
mcp_tools_to_specs,
|
|
108
|
+
write_mcp_config,
|
|
109
|
+
)
|
|
110
|
+
from aria_code.packages.aria_services import (
|
|
111
|
+
list_service_specs,
|
|
112
|
+
list_service_usage_specs,
|
|
113
|
+
required_service_names,
|
|
114
|
+
)
|
|
115
|
+
from aria_code.packages.aria_services.provider_health import GLOBAL_PROVIDER_HEALTH
|
|
116
|
+
from aria_code.packages.aria_skills import builtin_skill_specs
|
|
117
|
+
from aria_code.packages.aria_tools import build_registry_from_legacy
|
|
118
|
+
except Exception as exc:
|
|
119
|
+
print_error(self.context, f"packages facade unavailable: {exc}")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
sub = args.strip().lower()
|
|
123
|
+
identity = aria_code_identity(_get___version__())
|
|
124
|
+
arthera = discover_arthera_packages()
|
|
125
|
+
tool_registry = build_registry_from_legacy(_get_LOCAL_TOOLS(), _get_LOCAL_TOOL_SCHEMAS())
|
|
126
|
+
services = list_service_specs()
|
|
127
|
+
agent_count = len(list_agent_manifests())
|
|
128
|
+
skill_count = len(builtin_skill_specs())
|
|
129
|
+
mcp_exposure_count = len(default_exposures())
|
|
130
|
+
try:
|
|
131
|
+
server_cfg = arthera_quant_engine_server_config()
|
|
132
|
+
except ValueError:
|
|
133
|
+
server_cfg = None
|
|
134
|
+
mcp_config_path = _get_MCP_CONFIG_PATH() if _get__HAS_MCP() else aria_home() / "mcp_servers.json"
|
|
135
|
+
|
|
136
|
+
if sub.startswith("export-manifest") or sub.startswith("manifest"):
|
|
137
|
+
raw_parts = args.strip().split(maxsplit=1)
|
|
138
|
+
out_path = None
|
|
139
|
+
if len(raw_parts) > 1:
|
|
140
|
+
out_path = pathlib.Path(raw_parts[1]).expanduser()
|
|
141
|
+
else:
|
|
142
|
+
from aria_code.artifacts import artifact_dir
|
|
143
|
+
out_path = artifact_dir("manifests", "packages") / "aria_package_manifest.json"
|
|
144
|
+
|
|
145
|
+
reg = getattr(self.terminal, "_mcp_registry", None)
|
|
146
|
+
mcp_tools = []
|
|
147
|
+
if reg:
|
|
148
|
+
try:
|
|
149
|
+
mcp_tools = [
|
|
150
|
+
tool for tool in reg.all_tools()
|
|
151
|
+
if tool.get("server") == "arthera_quant_engine"
|
|
152
|
+
]
|
|
153
|
+
except Exception:
|
|
154
|
+
mcp_tools = []
|
|
155
|
+
arthera_specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
|
|
156
|
+
manifest = build_package_manifest(
|
|
157
|
+
identity=identity,
|
|
158
|
+
services=services,
|
|
159
|
+
tools=tool_registry.list(),
|
|
160
|
+
agents=list_agent_manifests(),
|
|
161
|
+
skills=builtin_skill_specs(),
|
|
162
|
+
mcp_exposures=default_exposures(),
|
|
163
|
+
arthera_packages=arthera,
|
|
164
|
+
arthera_mcp_tools=arthera_specs,
|
|
165
|
+
)
|
|
166
|
+
write_package_manifest(out_path, manifest)
|
|
167
|
+
if self.context.has_rich:
|
|
168
|
+
self.context.console.print()
|
|
169
|
+
self.context.console.print(f" [green]Package manifest exported[/green] [dim]{out_path}[/dim]")
|
|
170
|
+
self.context.console.print(
|
|
171
|
+
f" [dim]tools={len(manifest['capabilities']['tools'])} "
|
|
172
|
+
f"services={len(manifest['capabilities']['services'])} "
|
|
173
|
+
f"agents={len(manifest['capabilities']['agents'])} "
|
|
174
|
+
f"skills={len(manifest['capabilities']['skills'])} "
|
|
175
|
+
f"arthera_mcp_tools={len(manifest['capabilities']['arthera_mcp_tools'])}[/dim]\n"
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
print(f"Package manifest exported: {out_path}")
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
if sub in ("doctor", "doctor arthera", "arthera doctor"):
|
|
182
|
+
reg = getattr(self.terminal, "_mcp_registry", None)
|
|
183
|
+
runtime_status = []
|
|
184
|
+
mcp_tools = []
|
|
185
|
+
if reg:
|
|
186
|
+
try:
|
|
187
|
+
runtime_status = reg.status()
|
|
188
|
+
mcp_tools = [
|
|
189
|
+
tool for tool in reg.all_tools()
|
|
190
|
+
if tool.get("server") == "arthera_quant_engine"
|
|
191
|
+
]
|
|
192
|
+
except Exception:
|
|
193
|
+
runtime_status = []
|
|
194
|
+
mcp_tools = []
|
|
195
|
+
status = mcp_server_status(mcp_config_path, "arthera_quant_engine", runtime_status)
|
|
196
|
+
specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
|
|
197
|
+
from aria_code.artifacts import artifact_dir
|
|
198
|
+
manifest_path = artifact_dir("manifests", "packages", create=False) / "aria_package_manifest.json"
|
|
199
|
+
report = build_package_doctor_report(
|
|
200
|
+
arthera=arthera,
|
|
201
|
+
mcp_status=status,
|
|
202
|
+
tool_count=len(specs),
|
|
203
|
+
manifest_can_export=True,
|
|
204
|
+
manifest_path=manifest_path,
|
|
205
|
+
services=services,
|
|
206
|
+
required_services=required_service_names(),
|
|
207
|
+
architecture_layers=list_architecture_layers(),
|
|
208
|
+
required_architecture_layers=required_architecture_layer_names(),
|
|
209
|
+
provider_health=GLOBAL_PROVIDER_HEALTH.snapshot(),
|
|
210
|
+
)
|
|
211
|
+
if self.context.has_rich:
|
|
212
|
+
from rich.table import Table as _Table
|
|
213
|
+
color = "green" if report.status == "ok" else "yellow" if report.status == "warn" else "red"
|
|
214
|
+
self.context.console.print()
|
|
215
|
+
self.context.console.print(
|
|
216
|
+
f" [bold]{identity.product}[/bold] "
|
|
217
|
+
f"[dim]· {identity.company} packages doctor ·[/dim] [{color}]{report.status}[/{color}]\n"
|
|
218
|
+
)
|
|
219
|
+
tbl = _Table(
|
|
220
|
+
box=_get_rich_box().ROUNDED,
|
|
221
|
+
border_style="dim",
|
|
222
|
+
show_header=True,
|
|
223
|
+
header_style="bold dim",
|
|
224
|
+
)
|
|
225
|
+
tbl.add_column("Check", width=22)
|
|
226
|
+
tbl.add_column("Status", width=8)
|
|
227
|
+
tbl.add_column("Detail")
|
|
228
|
+
tbl.add_column("Next")
|
|
229
|
+
for check in report.checks:
|
|
230
|
+
st_color = "green" if check.status == "ok" else "yellow" if check.status == "warn" else "red"
|
|
231
|
+
tbl.add_row(
|
|
232
|
+
check.name,
|
|
233
|
+
f"[{st_color}]{check.status}[/{st_color}]",
|
|
234
|
+
check.detail,
|
|
235
|
+
check.remediation,
|
|
236
|
+
)
|
|
237
|
+
self.context.console.print(tbl)
|
|
238
|
+
self.context.console.print()
|
|
239
|
+
else:
|
|
240
|
+
print(f"{identity.product} packages doctor: {report.status}")
|
|
241
|
+
for check in report.checks:
|
|
242
|
+
print(f"{check.status:5s} {check.name}: {check.detail} {check.remediation}")
|
|
243
|
+
return
|
|
244
|
+
|
|
245
|
+
if sub in ("status", "status arthera", "arthera status"):
|
|
246
|
+
runtime_status = []
|
|
247
|
+
reg = getattr(self.terminal, "_mcp_registry", None)
|
|
248
|
+
if reg:
|
|
249
|
+
try:
|
|
250
|
+
runtime_status = reg.status()
|
|
251
|
+
except Exception:
|
|
252
|
+
runtime_status = []
|
|
253
|
+
status = mcp_server_status(mcp_config_path, "arthera_quant_engine", runtime_status)
|
|
254
|
+
|
|
255
|
+
if self.context.has_rich:
|
|
256
|
+
self.context.console.print()
|
|
257
|
+
self.context.console.print(
|
|
258
|
+
f" [bold]{identity.product}[/bold] "
|
|
259
|
+
f"[dim]· {identity.company} package bridge status[/dim]\n"
|
|
260
|
+
)
|
|
261
|
+
rows = [
|
|
262
|
+
("config", status["config_path"]),
|
|
263
|
+
("configured", "yes" if status["configured"] else "no"),
|
|
264
|
+
("server", status["server_path"] or "—"),
|
|
265
|
+
("server file", "found" if status["server_file_exists"] else "missing"),
|
|
266
|
+
("runtime", "running" if status["running"] else "not running"),
|
|
267
|
+
("tools", str(status["tool_count"])),
|
|
268
|
+
]
|
|
269
|
+
for key, value in rows:
|
|
270
|
+
style = "green" if value in ("yes", "found", "running") else "yellow" if value in ("no", "missing", "not running") else "dim"
|
|
271
|
+
self.context.console.print(f" [dim]{key:12s}[/dim] [{style}]{value}[/{style}]")
|
|
272
|
+
if status["tools"]:
|
|
273
|
+
self.context.console.print(" [dim]tool names:[/dim] " + ", ".join(status["tools"][:12]))
|
|
274
|
+
if not status["configured"]:
|
|
275
|
+
self.context.console.print(" [dim]Run /packages connect arthera to write the MCP bridge.[/dim]")
|
|
276
|
+
elif not status["running"]:
|
|
277
|
+
self.context.console.print(" [dim]Run /mcp reload or /packages connect arthera --reload.[/dim]")
|
|
278
|
+
self.context.console.print()
|
|
279
|
+
else:
|
|
280
|
+
print(f"{identity.product} · {identity.company} package bridge status")
|
|
281
|
+
for key, value in status.items():
|
|
282
|
+
if key != "tools":
|
|
283
|
+
print(f"{key}: {value}")
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
if sub in ("tools arthera", "arthera tools", "tools"):
|
|
287
|
+
reg = getattr(self.terminal, "_mcp_registry", None)
|
|
288
|
+
mcp_tools = []
|
|
289
|
+
if reg:
|
|
290
|
+
try:
|
|
291
|
+
mcp_tools = [
|
|
292
|
+
tool for tool in reg.all_tools()
|
|
293
|
+
if tool.get("server") == "arthera_quant_engine"
|
|
294
|
+
]
|
|
295
|
+
except Exception:
|
|
296
|
+
mcp_tools = []
|
|
297
|
+
specs = mcp_tools_to_specs(mcp_tools, "arthera_quant_engine")
|
|
298
|
+
|
|
299
|
+
if self.context.has_rich:
|
|
300
|
+
from rich.table import Table as _Table
|
|
301
|
+
self.context.console.print()
|
|
302
|
+
self.context.console.print(
|
|
303
|
+
f" [bold]{identity.product}[/bold] "
|
|
304
|
+
f"[dim]· {identity.company} MCP tool manifests[/dim]\n"
|
|
305
|
+
)
|
|
306
|
+
if not specs:
|
|
307
|
+
self.context.console.print(" [yellow]No Arthera MCP tools discovered.[/yellow]")
|
|
308
|
+
self.context.console.print(" [dim]Run /packages connect arthera --reload, then retry /packages tools arthera.[/dim]\n")
|
|
309
|
+
return
|
|
310
|
+
tbl = _Table(
|
|
311
|
+
title="[bold]Arthera QuantEngine Tools[/bold]",
|
|
312
|
+
box=_get_rich_box().ROUNDED,
|
|
313
|
+
border_style="dim",
|
|
314
|
+
show_header=True,
|
|
315
|
+
header_style="bold dim",
|
|
316
|
+
)
|
|
317
|
+
tbl.add_column("Tool", width=34)
|
|
318
|
+
tbl.add_column("Permissions", width=24)
|
|
319
|
+
tbl.add_column("Capabilities", width=30)
|
|
320
|
+
tbl.add_column("Schema")
|
|
321
|
+
for spec in specs:
|
|
322
|
+
perms = ", ".join(p.value for p in spec.permissions)
|
|
323
|
+
caps = ", ".join(spec.capabilities)
|
|
324
|
+
schema_state = "yes" if spec.schema else "no"
|
|
325
|
+
tbl.add_row(spec.name, perms, caps, schema_state)
|
|
326
|
+
self.context.console.print(tbl)
|
|
327
|
+
self.context.console.print()
|
|
328
|
+
else:
|
|
329
|
+
if not specs:
|
|
330
|
+
print("No Arthera MCP tools discovered. Run /packages connect arthera --reload.")
|
|
331
|
+
return
|
|
332
|
+
for spec in specs:
|
|
333
|
+
print(f"{spec.name}: {', '.join(spec.capabilities)}")
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
if sub in ("services", "service", "usage", "use", "map"):
|
|
337
|
+
usage_specs = list_service_usage_specs()
|
|
338
|
+
if self.context.has_rich:
|
|
339
|
+
from rich.table import Table as _Table
|
|
340
|
+
self.context.console.print()
|
|
341
|
+
self.context.console.print(
|
|
342
|
+
f" [bold]{identity.product}[/bold] "
|
|
343
|
+
f"[dim]· service usage map[/dim]\n"
|
|
344
|
+
)
|
|
345
|
+
tbl = _Table(
|
|
346
|
+
title="[bold]Project Services[/bold]",
|
|
347
|
+
box=_get_rich_box().ROUNDED,
|
|
348
|
+
border_style="dim",
|
|
349
|
+
show_header=True,
|
|
350
|
+
header_style="bold dim",
|
|
351
|
+
)
|
|
352
|
+
tbl.add_column("Service", width=22)
|
|
353
|
+
tbl.add_column("Purpose", width=34)
|
|
354
|
+
tbl.add_column("CLI", width=32)
|
|
355
|
+
tbl.add_column("Arthera / MCP", width=34)
|
|
356
|
+
tbl.add_column("Next")
|
|
357
|
+
for spec in usage_specs:
|
|
358
|
+
mcp = ", ".join(spec.mcp_tools[:4]) if spec.mcp_tools else "—"
|
|
359
|
+
packages = ", ".join(spec.package_sources[:2])
|
|
360
|
+
tbl.add_row(
|
|
361
|
+
spec.name,
|
|
362
|
+
spec.purpose,
|
|
363
|
+
", ".join(spec.cli_entrypoints),
|
|
364
|
+
f"{packages}\n[dim]{mcp}[/dim]",
|
|
365
|
+
spec.next_step,
|
|
366
|
+
)
|
|
367
|
+
self.context.console.print(tbl)
|
|
368
|
+
self.context.console.print("[dim]连接 Arthera MCP: /packages connect arthera --reload;券商接入: /broker guide[/dim]\n")
|
|
369
|
+
else:
|
|
370
|
+
for spec in usage_specs:
|
|
371
|
+
print(f"{spec.name}: {spec.purpose} -> {', '.join(spec.cli_entrypoints)}")
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
if sub.startswith("connect arthera") or sub in ("connect", "connect-quant", "connect quant"):
|
|
375
|
+
if server_cfg is None:
|
|
376
|
+
print_error(self.context, "Set an absolute ARTHERA_ROOT for the legacy bridge, or use /finance setup for the standalone financial-service.")
|
|
377
|
+
return
|
|
378
|
+
server_path = pathlib.Path(str(server_cfg["args"][0]))
|
|
379
|
+
if not server_path.is_file():
|
|
380
|
+
print_error(self.context, "The configured backend has no QuantEngine MCP server. No configuration was written.")
|
|
381
|
+
return
|
|
382
|
+
existing = load_mcp_config(mcp_config_path)
|
|
383
|
+
updated = merge_server_config(existing, server_cfg)
|
|
384
|
+
write_mcp_config(mcp_config_path, updated)
|
|
385
|
+
server_path = pathlib.Path(str(server_cfg["args"][0]))
|
|
386
|
+
ready = server_path.exists()
|
|
387
|
+
if self.context.has_rich:
|
|
388
|
+
self.context.console.print()
|
|
389
|
+
self.context.console.print(
|
|
390
|
+
f" [bold]{identity.product}[/bold] "
|
|
391
|
+
f"[dim]connected to {identity.company} package bridge[/dim]"
|
|
392
|
+
)
|
|
393
|
+
status = "[green]ready[/green]" if ready else "[yellow]configured, server file not found[/yellow]"
|
|
394
|
+
self.context.console.print(f" {status} [dim]{server_cfg['name']}[/dim]")
|
|
395
|
+
self.context.console.print(f" [dim]config:[/dim] {mcp_config_path}")
|
|
396
|
+
self.context.console.print(f" [dim]server:[/dim] {server_path}")
|
|
397
|
+
else:
|
|
398
|
+
print(f"Connected {identity.product} -> {server_cfg['name']}")
|
|
399
|
+
print(f"config: {mcp_config_path}")
|
|
400
|
+
print(f"server: {server_path} ({'ready' if ready else 'missing'})")
|
|
401
|
+
|
|
402
|
+
if "--reload" in sub or " reload" in sub:
|
|
403
|
+
await self.cmd_mcp("reload")
|
|
404
|
+
else:
|
|
405
|
+
if self.context.has_rich:
|
|
406
|
+
self.context.console.print(" [dim]Run /mcp reload to start the server.[/dim]\n")
|
|
407
|
+
else:
|
|
408
|
+
print("Run /mcp reload to start the server.")
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
if self.context.has_rich:
|
|
412
|
+
from rich.table import Table as _Table
|
|
413
|
+
|
|
414
|
+
self.context.console.print()
|
|
415
|
+
self.context.console.print(
|
|
416
|
+
f" [bold]{identity.product}[/bold] "
|
|
417
|
+
f"[dim]v{identity.version} · {identity.company} product[/dim]"
|
|
418
|
+
)
|
|
419
|
+
self.context.console.print(f" [dim]{identity.description}[/dim]\n")
|
|
420
|
+
|
|
421
|
+
tbl = _Table(
|
|
422
|
+
title="[bold]Package Facades[/bold]",
|
|
423
|
+
box=_get_rich_box().ROUNDED,
|
|
424
|
+
border_style="dim",
|
|
425
|
+
show_header=True,
|
|
426
|
+
header_style="bold dim",
|
|
427
|
+
)
|
|
428
|
+
tbl.add_column("Package")
|
|
429
|
+
tbl.add_column("Status")
|
|
430
|
+
tbl.add_column("Surface")
|
|
431
|
+
tbl.add_row("aria_core", "ready", "CapabilityManifest, permissions")
|
|
432
|
+
tbl.add_row("aria_services", "ready", f"{len(services)} service boundaries")
|
|
433
|
+
tbl.add_row("aria_tools", "ready", f"{len(tool_registry.list())} tools")
|
|
434
|
+
tbl.add_row("aria_agents", "ready", f"{agent_count} agents")
|
|
435
|
+
tbl.add_row("aria_skills", "ready", f"{skill_count} skills")
|
|
436
|
+
tbl.add_row("aria_mcp", "ready", f"{mcp_exposure_count} planned exposures")
|
|
437
|
+
tbl.add_row("aria_infra", "ready", "Arthera package discovery")
|
|
438
|
+
self.context.console.print(tbl)
|
|
439
|
+
|
|
440
|
+
self.context.console.print()
|
|
441
|
+
self.context.console.print("[bold]Arthera Packages[/bold]")
|
|
442
|
+
from aria_code.ui.render.output import display_path as _display_path
|
|
443
|
+
if arthera.available:
|
|
444
|
+
self.context.console.print(f" [green]found[/green] [dim]{_display_path(arthera.root, fallback='package root')}[/dim]")
|
|
445
|
+
for name, path in sorted(arthera.packages.items()):
|
|
446
|
+
self.context.console.print(f" [dim]·[/dim] [bold]{name:14s}[/bold] [dim]{_display_path(path, fallback='package')}[/dim]")
|
|
447
|
+
if arthera.mcp_servers:
|
|
448
|
+
self.context.console.print(" [dim]MCP server candidates:[/dim]")
|
|
449
|
+
for path in arthera.mcp_servers[:5]:
|
|
450
|
+
self.context.console.print(f" [dim]{_display_path(path, fallback='server')}[/dim]")
|
|
451
|
+
else:
|
|
452
|
+
self.context.console.print(f" [yellow]not found[/yellow] [dim]{_display_path(arthera.root, fallback='package root')}[/dim]")
|
|
453
|
+
|
|
454
|
+
self.context.console.print()
|
|
455
|
+
self.context.console.print("[bold]Recommended MCP bridge[/bold]")
|
|
456
|
+
if server_cfg is not None:
|
|
457
|
+
self.context.console.print(f" [dim]name:[/dim] {server_cfg['name']}")
|
|
458
|
+
self.context.console.print(f" [dim]command:[/dim] {server_cfg['command']} {' '.join(server_cfg['args'])}")
|
|
459
|
+
self.context.console.print(f" [dim]env PYTHONPATH:[/dim] {server_cfg['env']['PYTHONPATH']}")
|
|
460
|
+
self.context.console.print(f" [dim]config:[/dim] {mcp_config_path}")
|
|
461
|
+
self.context.console.print(" [dim]Run /packages connect arthera to write this MCP bridge.[/dim]\n")
|
|
462
|
+
else:
|
|
463
|
+
self.context.console.print(" [dim]No private source bridge configured. Use /finance setup for the standalone financial-service.[/dim]\n")
|
|
464
|
+
else:
|
|
465
|
+
print(f"{identity.product} v{identity.version} · {identity.company} product")
|
|
466
|
+
print(f"services={len(services)} tools={len(tool_registry.list())} agents={agent_count} skills={skill_count} mcp={mcp_exposure_count}")
|
|
467
|
+
print(f"Arthera packages: {'found' if arthera.available else 'not found'} {arthera.root}")
|
|
468
|
+
print(f"Recommended MCP: {server_cfg}")
|
|
469
|
+
|
|
470
|
+
async def cmd_file(self, args: str):
|
|
471
|
+
"""
|
|
472
|
+
/file load <路径> — 加载文件到会话
|
|
473
|
+
/file analyze [1|2|3|4] — 分层分析(1=摘要 2=深度 3=领域 4=建议)
|
|
474
|
+
/file ask <问题> — 就已加载文件提问
|
|
475
|
+
/file list — 列出会话中的所有文件
|
|
476
|
+
/file switch <文件名> — 切换活跃文件
|
|
477
|
+
/file clear [文件名] — 清除文件
|
|
478
|
+
/file check — 检查可用解析器
|
|
479
|
+
"""
|
|
480
|
+
import asyncio as _asyncio
|
|
481
|
+
loop = _asyncio.get_event_loop()
|
|
482
|
+
parts = args.strip().split(None, 1) if args.strip() else []
|
|
483
|
+
sub = parts[0].lower() if parts else "help"
|
|
484
|
+
rest = parts[1].strip() if len(parts) > 1 else ""
|
|
485
|
+
|
|
486
|
+
# ── 确保 file_session 已初始化 ────────────────────────────────────────
|
|
487
|
+
if self.terminal._file_session is None:
|
|
488
|
+
try:
|
|
489
|
+
from aria_code.file_analysis_tools import FileSession
|
|
490
|
+
self.terminal._file_session = FileSession()
|
|
491
|
+
except ImportError as e:
|
|
492
|
+
if self.context.has_rich:
|
|
493
|
+
self.context.console.print(f"[red]file_analysis_tools 未加载: {e}[/red]")
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
fs = self.terminal._file_session
|
|
497
|
+
|
|
498
|
+
# ────────────────── /file load ────────────────────────────────────────
|
|
499
|
+
if sub == "load":
|
|
500
|
+
if not rest:
|
|
501
|
+
if self.context.has_rich:
|
|
502
|
+
self.context.console.print("[dim]用法: /file load <文件路径>[/dim]")
|
|
503
|
+
self.context.console.print("[dim]支持: PDF DOCX XLSX CSV JSON TXT MD 图片 代码文件[/dim]")
|
|
504
|
+
return
|
|
505
|
+
|
|
506
|
+
if self.context.has_rich:
|
|
507
|
+
self.context.console.print(f"[dim]正在解析 {rest}...[/dim]")
|
|
508
|
+
|
|
509
|
+
# Include images only for vision-capable models
|
|
510
|
+
_curr_model = self.terminal.config.get("model", "")
|
|
511
|
+
include_img = False
|
|
512
|
+
if _get__HAS_MODEL_CAP():
|
|
513
|
+
try:
|
|
514
|
+
_mc = get_model_capability(_curr_model)
|
|
515
|
+
include_img = bool(_mc.vision)
|
|
516
|
+
except Exception:
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
from aria_code.file_analysis_tools import parse_file, check_parsers
|
|
520
|
+
fc = await loop.run_in_executor(
|
|
521
|
+
None, lambda: parse_file(rest, include_images=include_img))
|
|
522
|
+
|
|
523
|
+
if not fc.success:
|
|
524
|
+
if self.context.has_rich:
|
|
525
|
+
self.context.console.print(f"[red]解析失败: {fc.error}[/red]")
|
|
526
|
+
# Show which parsers are available
|
|
527
|
+
parsers = check_parsers()
|
|
528
|
+
missing = [k for k, v in parsers.items() if not v]
|
|
529
|
+
if missing:
|
|
530
|
+
self.context.console.print(f"[yellow]⚠ 未安装解析器: {', '.join(missing)}[/yellow]")
|
|
531
|
+
self.context.console.print(f"[dim]安装命令: pip install {' '.join(missing)}[/dim]")
|
|
532
|
+
return
|
|
533
|
+
|
|
534
|
+
fs.load(rest, include_images=include_img)
|
|
535
|
+
self.terminal._file_ctx_injected = False # Reset so next msg injects file
|
|
536
|
+
|
|
537
|
+
if self.context.has_rich:
|
|
538
|
+
from rich.panel import Panel as _P
|
|
539
|
+
from rich import box as _box
|
|
540
|
+
info_lines = [
|
|
541
|
+
f"[green]✓[/green] [bold]{fc.filename}[/bold]",
|
|
542
|
+
f"[dim]类型: {fc.file_type.upper()} 大小: {fc.size_kb:.1f} KB "
|
|
543
|
+
f"提取: {fc.char_count:,} 字符[/dim]",
|
|
544
|
+
]
|
|
545
|
+
for k, v in fc.metadata.items():
|
|
546
|
+
if k in ("pages","rows","columns","lines","language",
|
|
547
|
+
"sheets","title","author","symbols"):
|
|
548
|
+
val = v[:5] if isinstance(v, list) else v
|
|
549
|
+
info_lines.append(f"[dim]{k}: {val}[/dim]")
|
|
550
|
+
if fc.truncated:
|
|
551
|
+
info_lines.append("[yellow]⚠ 内容已截断(文件较大)[/yellow]")
|
|
552
|
+
if fc.tables:
|
|
553
|
+
info_lines.append(f"[dim]包含 {len(fc.tables)} 个表格[/dim]")
|
|
554
|
+
info_lines.append("\n[dim]发送任何消息即可开始分析,或使用 /file analyze 1-4[/dim]")
|
|
555
|
+
self.context.console.print(_P("\n".join(info_lines),
|
|
556
|
+
title="[bold]📄 文件已加载[/bold]",
|
|
557
|
+
border_style="green", box=_box.ROUNDED))
|
|
558
|
+
|
|
559
|
+
# ────────────────── /file analyze ─────────────────────────────────────
|
|
560
|
+
elif sub == "analyze":
|
|
561
|
+
fc = fs.get_active()
|
|
562
|
+
if not fc:
|
|
563
|
+
if self.context.has_rich: self.context.console.print("[dim]请先使用 /file load <路径> 加载文件[/dim]")
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
# Determine layer(s) to run
|
|
567
|
+
layer_arg = rest.strip()
|
|
568
|
+
if layer_arg == "all":
|
|
569
|
+
layers_to_run = [1, 2, 3, 4]
|
|
570
|
+
else:
|
|
571
|
+
try:
|
|
572
|
+
layers_to_run = [int(layer_arg)] if layer_arg else [1, 2]
|
|
573
|
+
except ValueError:
|
|
574
|
+
layers_to_run = [1, 2]
|
|
575
|
+
|
|
576
|
+
layer_names = {1: "📌 快速摘要", 2: "🔍 深度分析", 3: "💡 领域洞察", 4: "✅ 行动建议"}
|
|
577
|
+
|
|
578
|
+
from aria_code.file_analysis_tools import build_analysis_prompt
|
|
579
|
+
|
|
580
|
+
for layer in layers_to_run:
|
|
581
|
+
if self.context.has_rich:
|
|
582
|
+
self.context.console.print(f"\n[bold]{layer_names.get(layer, f'层{layer}')}[/bold]")
|
|
583
|
+
self.context.console.print(f"[dim]{'─'*50}[/dim]")
|
|
584
|
+
|
|
585
|
+
prompt = build_analysis_prompt(fc, layer=layer)
|
|
586
|
+
# Send to LLM via the normal message pipeline
|
|
587
|
+
await self.terminal.send_message(
|
|
588
|
+
prompt,
|
|
589
|
+
system_override=(
|
|
590
|
+
"你是专业文档分析助手,具备金融、法律、技术、不动产等多领域知识。"
|
|
591
|
+
"分析要精确、结构化,优先使用数字和具体事实。"
|
|
592
|
+
),
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
if len(layers_to_run) > 1 and layer < layers_to_run[-1]:
|
|
596
|
+
if self.context.has_rich:
|
|
597
|
+
self.context.console.print(f"\n[dim]{'═'*60}[/dim]")
|
|
598
|
+
self.context.console.print("[dim]进入下一层分析...[/dim]\n")
|
|
599
|
+
|
|
600
|
+
# ────────────────── /file ask ──────────────────────────────────────────
|
|
601
|
+
elif sub == "ask":
|
|
602
|
+
fc = fs.get_active()
|
|
603
|
+
if not fc:
|
|
604
|
+
if self.context.has_rich: self.context.console.print("[dim]请先使用 /file load <路径> 加载文件[/dim]")
|
|
605
|
+
return
|
|
606
|
+
question = rest.strip()
|
|
607
|
+
if not question:
|
|
608
|
+
if self.context.has_rich:
|
|
609
|
+
from rich.prompt import Prompt as _Prompt
|
|
610
|
+
question = _Prompt.ask(" 请输入问题")
|
|
611
|
+
else:
|
|
612
|
+
question = input("请输入问题: ")
|
|
613
|
+
if not question:
|
|
614
|
+
return
|
|
615
|
+
|
|
616
|
+
from aria_code.file_analysis_tools import build_analysis_prompt
|
|
617
|
+
prompt = build_analysis_prompt(fc, layer=0, question=question)
|
|
618
|
+
await self.terminal.send_message(
|
|
619
|
+
prompt,
|
|
620
|
+
system_override=(
|
|
621
|
+
"你是专业文档分析助手。请基于用户提供的文件内容准确回答问题,"
|
|
622
|
+
"若文件中无法找到答案,请如实说明。"
|
|
623
|
+
),
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# ────────────────── /file list ─────────────────────────────────────────
|
|
627
|
+
elif sub == "list":
|
|
628
|
+
files = fs.list_files()
|
|
629
|
+
if not files:
|
|
630
|
+
if self.context.has_rich: self.context.console.print("[dim]会话中暂无已加载文件。使用 /file load <路径>[/dim]")
|
|
631
|
+
return
|
|
632
|
+
if self.context.has_rich:
|
|
633
|
+
from rich.table import Table as _T
|
|
634
|
+
from rich import box as _box
|
|
635
|
+
tb = _T(title="[bold]📂 已加载文件[/bold]", box=_box.ROUNDED)
|
|
636
|
+
tb.add_column("状态", width=4); tb.add_column("文件名")
|
|
637
|
+
tb.add_column("类型", style="dim"); tb.add_column("大小KB", justify="right", style="dim")
|
|
638
|
+
tb.add_column("字符数", justify="right", style="dim"); tb.add_column("截断", style="dim")
|
|
639
|
+
for f in files:
|
|
640
|
+
status = "[green]●[/green]" if f["active"] else "[dim]○[/dim]"
|
|
641
|
+
tb.add_row(status, f["filename"], f["type"].upper(),
|
|
642
|
+
str(f["size_kb"]), f"{f['chars']:,}",
|
|
643
|
+
"[yellow]是[/yellow]" if f["truncated"] else "否")
|
|
644
|
+
self.context.console.print(tb)
|
|
645
|
+
self.context.console.print("[dim]/file ask <问题> 向活跃文件提问[/dim]")
|
|
646
|
+
|
|
647
|
+
# ────────────────── /file switch ──────────────────────────────────────
|
|
648
|
+
elif sub == "switch":
|
|
649
|
+
if not rest:
|
|
650
|
+
if self.context.has_rich: self.context.console.print("[dim]用法: /file switch <文件名>[/dim]")
|
|
651
|
+
return
|
|
652
|
+
if fs.set_active(rest):
|
|
653
|
+
fc = fs.get_active()
|
|
654
|
+
self.terminal._file_ctx_injected = False
|
|
655
|
+
if self.context.has_rich:
|
|
656
|
+
self.context.console.print(f"[green]✓ 已切换到: {fc.filename}[/green]")
|
|
657
|
+
else:
|
|
658
|
+
if self.context.has_rich: self.context.console.print(f"[red]未找到文件: {rest}[/red]")
|
|
659
|
+
|
|
660
|
+
# ────────────────── /file clear ──────────────────────────────────────
|
|
661
|
+
elif sub == "clear":
|
|
662
|
+
fs.clear(rest if rest else None)
|
|
663
|
+
self.terminal._file_ctx_injected = False
|
|
664
|
+
msg = f"已清除文件: {rest}" if rest else "已清除所有已加载文件"
|
|
665
|
+
if self.context.has_rich: self.context.console.print(f"[dim]{msg}[/dim]")
|
|
666
|
+
|
|
667
|
+
# ────────────────── /file check ───────────────────────────────────────
|
|
668
|
+
elif sub == "check":
|
|
669
|
+
from aria_code.file_analysis_tools import check_parsers
|
|
670
|
+
parsers = check_parsers()
|
|
671
|
+
if self.context.has_rich:
|
|
672
|
+
from rich.table import Table as _T
|
|
673
|
+
from rich import box as _box
|
|
674
|
+
tb = _T(title="[bold]📦 文件解析器状态[/bold]", box=_box.ROUNDED)
|
|
675
|
+
tb.add_column("库"); tb.add_column("状态"); tb.add_column("安装命令", style="dim")
|
|
676
|
+
_CMDS = {
|
|
677
|
+
"pdfplumber": "pip install pdfplumber",
|
|
678
|
+
"pypdf": "pip install pypdf",
|
|
679
|
+
"python-docx":"pip install python-docx",
|
|
680
|
+
"pandas": "pip install pandas",
|
|
681
|
+
"openpyxl": "pip install openpyxl",
|
|
682
|
+
"beautifulsoup4": "pip install beautifulsoup4",
|
|
683
|
+
"Pillow": "pip install Pillow",
|
|
684
|
+
}
|
|
685
|
+
for lib, ok in parsers.items():
|
|
686
|
+
status = "[green]✓ 已安装[/green]" if ok else "[red]✗ 未安装[/red]"
|
|
687
|
+
tb.add_row(lib, status, "" if ok else _CMDS.get(lib,""))
|
|
688
|
+
self.context.console.print(tb)
|
|
689
|
+
formats_ok = []
|
|
690
|
+
if parsers.get("pdfplumber") or parsers.get("pypdf"):
|
|
691
|
+
formats_ok.append("PDF")
|
|
692
|
+
if parsers.get("python-docx"):
|
|
693
|
+
formats_ok.append("Word/DOCX")
|
|
694
|
+
if parsers.get("pandas") and parsers.get("openpyxl"):
|
|
695
|
+
formats_ok.append("Excel/CSV")
|
|
696
|
+
formats_ok.extend(["JSON", "TXT/MD", "代码文件"])
|
|
697
|
+
self.context.console.print(f"[dim]可解析格式: {', '.join(formats_ok)}[/dim]")
|
|
698
|
+
|
|
699
|
+
# ────────────────── /file help ─────────────────────────────────────────
|
|
700
|
+
else:
|
|
701
|
+
if self.context.has_rich:
|
|
702
|
+
self.context.console.print("[bold]📄 /file 文件分析命令[/bold]")
|
|
703
|
+
rows = [
|
|
704
|
+
("/file load <路径>", "加载文件 (PDF/DOCX/XLSX/CSV/JSON/TXT/代码/图片)"),
|
|
705
|
+
("/file analyze 1", "快速摘要 (300字)"),
|
|
706
|
+
("/file analyze 2", "深度内容分析 (结构/要点/异常)"),
|
|
707
|
+
("/file analyze 3", "领域专项分析 (财务/法律/技术/不动产)"),
|
|
708
|
+
("/file analyze 4", "行动建议与风险清单"),
|
|
709
|
+
("/file analyze all", "依次运行 4 层分析"),
|
|
710
|
+
("/file ask <问题>", "就文件内容多轮提问"),
|
|
711
|
+
("/file list", "查看已加载文件"),
|
|
712
|
+
("/file switch <文件名>", "切换分析目标文件"),
|
|
713
|
+
("/file clear", "清除所有已加载文件"),
|
|
714
|
+
("/file check", "检查已安装的解析器"),
|
|
715
|
+
]
|
|
716
|
+
from rich.table import Table as _T
|
|
717
|
+
from rich import box as _box
|
|
718
|
+
tb = _T(box=_box.MINIMAL)
|
|
719
|
+
tb.add_column("命令", style="cyan"); tb.add_column("说明", style="dim")
|
|
720
|
+
for cmd, desc in rows:
|
|
721
|
+
tb.add_row(cmd, desc)
|
|
722
|
+
self.context.console.print(tb)
|
|
723
|
+
|
|
724
|
+
async def cmd_project(self, args: str):
|
|
725
|
+
"""项目分析 (Claude Code / Codex 风格): /project load|tree|grep|ask|task|status|info <参数>"""
|
|
726
|
+
# 本方法体内三处用到 _T / _box,但导入只写在别的方法里。普通方法的
|
|
727
|
+
# __globals__ 被 _rebind_mixin_globals 换成了 aria_cli 的,本文件模块级
|
|
728
|
+
# 的名字一律看不见——所以必须在方法内自己导入,跟本文件其它方法一致。
|
|
729
|
+
from rich.table import Table as _T
|
|
730
|
+
from rich import box as _box
|
|
731
|
+
try:
|
|
732
|
+
from aria_code.project_tools import ProjectSession, scan_project, format_grep_results
|
|
733
|
+
_HAS_PT = True
|
|
734
|
+
except ImportError:
|
|
735
|
+
_HAS_PT = False
|
|
736
|
+
|
|
737
|
+
if not _HAS_PT:
|
|
738
|
+
self.context.console.print("[red]❌ project_tools.py 未找到,请确保文件存在。[/red]")
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
parts = args.strip().split(maxsplit=1)
|
|
742
|
+
sub = parts[0].lower() if parts else "info"
|
|
743
|
+
rest = parts[1].strip() if len(parts) > 1 else ""
|
|
744
|
+
ps = self.terminal._project_session # type: ignore[attr-defined]
|
|
745
|
+
|
|
746
|
+
# ── load ──────────────────────────────────────────────────────────────
|
|
747
|
+
if sub == "load":
|
|
748
|
+
if not rest:
|
|
749
|
+
self.context.console.print("[yellow]用法: /project load <目录路径>[/yellow]")
|
|
750
|
+
return
|
|
751
|
+
from pathlib import Path as _Path
|
|
752
|
+
target = _Path(rest).expanduser().resolve()
|
|
753
|
+
if not target.exists():
|
|
754
|
+
self.context.console.print(f"[red]路径不存在: {target}[/red]")
|
|
755
|
+
return
|
|
756
|
+
|
|
757
|
+
self.context.console.print(f"[dim]正在扫描项目: {target} …[/dim]")
|
|
758
|
+
try:
|
|
759
|
+
new_ps = scan_project(str(target), max_files=2000)
|
|
760
|
+
except Exception as e:
|
|
761
|
+
self.context.console.print(f"[red]扫描失败: {e}[/red]")
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
self.terminal._project_session = new_ps # type: ignore[attr-defined]
|
|
765
|
+
self.terminal._project_ctx_injected = False # type: ignore[attr-defined]
|
|
766
|
+
|
|
767
|
+
# Auto-archive project into global memory
|
|
768
|
+
if getattr(self.terminal, "memory_mgr", None):
|
|
769
|
+
try:
|
|
770
|
+
_ps_s = new_ps.summary()
|
|
771
|
+
self.terminal.memory_mgr.upsert_project(_ps_s["name"], {
|
|
772
|
+
"root": _ps_s["root"],
|
|
773
|
+
"type": _ps_s["type"],
|
|
774
|
+
"languages": _ps_s.get("languages", []),
|
|
775
|
+
})
|
|
776
|
+
except Exception:
|
|
777
|
+
pass
|
|
778
|
+
|
|
779
|
+
s = new_ps.summary()
|
|
780
|
+
tb = _T(box=_box.ROUNDED, show_header=False, padding=(0, 1))
|
|
781
|
+
tb.add_column("k", style="dim", width=14)
|
|
782
|
+
tb.add_column("v", style="cyan")
|
|
783
|
+
tb.add_row("项目名", s["name"])
|
|
784
|
+
tb.add_row("路径", s["root"])
|
|
785
|
+
tb.add_row("类型", s["type"])
|
|
786
|
+
tb.add_row("语言", ", ".join(s["languages"][:4]))
|
|
787
|
+
tb.add_row("文件数", str(s["total_files"]))
|
|
788
|
+
tb.add_row("代码行", f"{s['total_lines']:,}")
|
|
789
|
+
tb.add_row("总大小", f"{s['total_size_kb']} KB")
|
|
790
|
+
if s["git"].get("branch"):
|
|
791
|
+
tb.add_row("Git 分支", s["git"]["branch"])
|
|
792
|
+
if s["git"].get("changed_count"):
|
|
793
|
+
tb.add_row("变更文件", str(s["git"]["changed_count"]))
|
|
794
|
+
self.context.console.print("\n[bold]项目已加载 ✓[/bold]")
|
|
795
|
+
self.context.console.print(tb)
|
|
796
|
+
self.context.console.print(f"\n[dim]关键文件: {', '.join(s['key_files'][:6])}[/dim]")
|
|
797
|
+
self.context.console.print("[dim]现在可以直接对话,Aria 将根据项目上下文回答。[/dim]\n")
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
# ── 未加载时提示 ──────────────────────────────────────────────────────
|
|
801
|
+
if ps is None:
|
|
802
|
+
self.context.console.print("[yellow]请先加载项目: /project load <目录路径>[/yellow]")
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
# ── tree ──────────────────────────────────────────────────────────────
|
|
806
|
+
if sub == "tree":
|
|
807
|
+
depth_arg = rest.strip()
|
|
808
|
+
max_lines = 120
|
|
809
|
+
if depth_arg.isdigit():
|
|
810
|
+
max_lines = int(depth_arg) * 30 # rough approximation
|
|
811
|
+
tree_str = ps.get_tree(max_lines=max_lines)
|
|
812
|
+
self.context.console.print(f"\n[bold]{ps.name}/[/bold]")
|
|
813
|
+
self.context.console.print(f"[dim]{tree_str}[/dim]")
|
|
814
|
+
self.context.console.print(f"\n[dim]共 {ps.stats.get('total_files', 0)} 个文件[/dim]\n")
|
|
815
|
+
|
|
816
|
+
# ── grep / search ─────────────────────────────────────────────────────
|
|
817
|
+
elif sub in ("grep", "search"):
|
|
818
|
+
if not rest:
|
|
819
|
+
self.context.console.print("[yellow]用法: /project grep <正则表达式> [glob模式][/yellow]")
|
|
820
|
+
return
|
|
821
|
+
parts2 = rest.split(maxsplit=1)
|
|
822
|
+
pattern = parts2[0]
|
|
823
|
+
glob = parts2[1] if len(parts2) > 1 else "**/*"
|
|
824
|
+
self.context.console.print(f"[dim]搜索 \"{pattern}\" …[/dim]")
|
|
825
|
+
results = ps.grep(pattern, glob=glob, max_results=60)
|
|
826
|
+
self.context.console.print(format_grep_results(results, pattern))
|
|
827
|
+
|
|
828
|
+
# ── status ────────────────────────────────────────────────────────────
|
|
829
|
+
elif sub == "status":
|
|
830
|
+
gi = ps.git_info
|
|
831
|
+
if not gi:
|
|
832
|
+
self.context.console.print("[dim]当前项目不是 Git 仓库[/dim]")
|
|
833
|
+
return
|
|
834
|
+
self.context.console.print(f"\n[bold]Git 状态[/bold] — {ps.name}")
|
|
835
|
+
self.context.console.print(f" 分支: [cyan]{gi.get('branch','?')}[/cyan] "
|
|
836
|
+
f"变更: [yellow]{gi.get('changed_count', 0)}[/yellow] 个文件")
|
|
837
|
+
if gi.get("changed_files"):
|
|
838
|
+
for f in gi["changed_files"][:15]:
|
|
839
|
+
self.context.console.print(f" [dim]{f}[/dim]")
|
|
840
|
+
if gi.get("recent_commits"):
|
|
841
|
+
self.context.console.print("\n[bold]最近提交:[/bold]")
|
|
842
|
+
for c in gi["recent_commits"][:5]:
|
|
843
|
+
self.context.console.print(f" [dim]{c}[/dim]")
|
|
844
|
+
self.context.console.print()
|
|
845
|
+
|
|
846
|
+
# ── info ──────────────────────────────────────────────────────────────
|
|
847
|
+
elif sub in ("info", "summary", ""):
|
|
848
|
+
s = ps.summary()
|
|
849
|
+
tb = _T(box=_box.ROUNDED, show_header=False, padding=(0, 1))
|
|
850
|
+
tb.add_column("k", style="dim", width=14)
|
|
851
|
+
tb.add_column("v", style="cyan")
|
|
852
|
+
tb.add_row("项目名", s["name"])
|
|
853
|
+
tb.add_row("路径", s["root"])
|
|
854
|
+
tb.add_row("类型", s["type"])
|
|
855
|
+
tb.add_row("主要语言", ", ".join(s["languages"][:4]))
|
|
856
|
+
tb.add_row("文件数", str(s["total_files"]))
|
|
857
|
+
tb.add_row("代码行", f"{s['total_lines']:,}")
|
|
858
|
+
tb.add_row("大小", f"{s['total_size_kb']} KB")
|
|
859
|
+
if s["git"].get("branch"):
|
|
860
|
+
tb.add_row("Git 分支", s["git"]["branch"])
|
|
861
|
+
self.context.console.print(tb)
|
|
862
|
+
self.context.console.print(f"\n[dim]关键文件: {', '.join(s['key_files'][:8])}[/dim]\n")
|
|
863
|
+
|
|
864
|
+
# ── read ──────────────────────────────────────────────────────────────
|
|
865
|
+
elif sub == "read":
|
|
866
|
+
if not rest:
|
|
867
|
+
self.context.console.print("[yellow]用法: /project read <文件路径>[/yellow]")
|
|
868
|
+
return
|
|
869
|
+
ok, content = ps.read_file(rest)
|
|
870
|
+
if not ok:
|
|
871
|
+
self.context.console.print(f"[red]{content}[/red]")
|
|
872
|
+
return
|
|
873
|
+
lang = rest.rsplit(".", 1)[-1] if "." in rest else "text"
|
|
874
|
+
self.context.console.print(f"\n[bold]{rest}[/bold]")
|
|
875
|
+
if self.context.has_rich:
|
|
876
|
+
from rich.syntax import Syntax
|
|
877
|
+
self.context.console.print(Syntax(content, lang, theme="monokai", line_numbers=True,
|
|
878
|
+
word_wrap=False))
|
|
879
|
+
else:
|
|
880
|
+
print(content)
|
|
881
|
+
|
|
882
|
+
# ── clear ─────────────────────────────────────────────────────────────
|
|
883
|
+
elif sub == "clear":
|
|
884
|
+
self.terminal._project_session = None # type: ignore[attr-defined]
|
|
885
|
+
self.terminal._project_ctx_injected = False # type: ignore[attr-defined]
|
|
886
|
+
self.context.console.print("[dim]项目上下文已清除[/dim]")
|
|
887
|
+
|
|
888
|
+
# ── ask / task → forward to AI with project context ───────────────────
|
|
889
|
+
elif sub in ("ask", "task"):
|
|
890
|
+
if not rest:
|
|
891
|
+
self.context.console.print(f"[yellow]用法: /project {sub} <问题或任务描述>[/yellow]")
|
|
892
|
+
return
|
|
893
|
+
# Delegate to the AI; project context is injected automatically via send_message
|
|
894
|
+
prefix = "请基于当前项目完成以下任务:\n" if sub == "task" else ""
|
|
895
|
+
await self.terminal.send_message(prefix + rest) # type: ignore[attr-defined]
|
|
896
|
+
|
|
897
|
+
# ── help ─────────────────────────────────────────────────────────────
|
|
898
|
+
else:
|
|
899
|
+
rows = [
|
|
900
|
+
("/project load <path>", "加载项目目录,构建文件索引"),
|
|
901
|
+
("/project tree [depth]", "显示文件树结构"),
|
|
902
|
+
("/project grep <pattern>", "跨文件正则搜索"),
|
|
903
|
+
("/project read <file>", "查看项目中的文件内容"),
|
|
904
|
+
("/project status", "Git 状态 + 最近提交"),
|
|
905
|
+
("/project info", "项目摘要(类型/语言/规模)"),
|
|
906
|
+
("/project ask <question>", "向 AI 提问(使用项目上下文)"),
|
|
907
|
+
("/project task <description>", "让 AI 执行任务(工具调用模式)"),
|
|
908
|
+
("/project clear", "卸载当前项目上下文"),
|
|
909
|
+
]
|
|
910
|
+
tb = _T(box=_box.MINIMAL)
|
|
911
|
+
tb.add_column("命令", style="cyan")
|
|
912
|
+
tb.add_column("说明", style="dim")
|
|
913
|
+
for cmd, desc in rows:
|
|
914
|
+
tb.add_row(cmd, desc)
|
|
915
|
+
self.context.console.print("\n[bold]/project — 项目分析命令[/bold]\n")
|
|
916
|
+
self.context.console.print(tb)
|
|
917
|
+
self.context.console.print()
|
|
918
|
+
|
|
919
|
+
async def cmd_init(self, args: str):
|
|
920
|
+
"""Bootstrap an ARIA.md memory file, or scaffold a new project.
|
|
921
|
+
|
|
922
|
+
Without arguments: scans the current directory and generates ARIA.md.
|
|
923
|
+
With a template name: creates a fully-runnable project scaffold.
|
|
924
|
+
|
|
925
|
+
Usage:
|
|
926
|
+
/init — generate ARIA.md for current project
|
|
927
|
+
/init --force — regenerate even if ARIA.md already exists
|
|
928
|
+
/init list — list available scaffold templates
|
|
929
|
+
/init quant [dir] — quantitative strategy project
|
|
930
|
+
/init analysis [dir] — data analysis project
|
|
931
|
+
/init fastapi [dir] — FastAPI financial data service
|
|
932
|
+
/init dashboard [dir] — Plotly Dash interactive dashboard
|
|
933
|
+
"""
|
|
934
|
+
global _PROJECT_CONTEXT
|
|
935
|
+
cwd = pathlib.Path.cwd()
|
|
936
|
+
|
|
937
|
+
# ── /init <template> — project scaffold ──────────────────────────────
|
|
938
|
+
_tmpl_key = args.strip().lower().split()[0] if args.strip() else ""
|
|
939
|
+
if _tmpl_key == "list":
|
|
940
|
+
rows = [(k, v["desc"]) for k, v in self._SCAFFOLD_TEMPLATES.items()]
|
|
941
|
+
if self.context.has_rich:
|
|
942
|
+
from rich.table import Table as _Table
|
|
943
|
+
t = _Table(box=None, show_header=True, header_style="bold cyan", padding=(0,2))
|
|
944
|
+
t.add_column("模板", style="green")
|
|
945
|
+
t.add_column("说明")
|
|
946
|
+
for k, d in rows:
|
|
947
|
+
t.add_row(k, d)
|
|
948
|
+
self.context.console.print("\n [bold]可用脚手架模板[/bold]")
|
|
949
|
+
self.context.console.print(t)
|
|
950
|
+
self.context.console.print("\n 用法: [cyan]/init <模板名> [目录名][/cyan]\n")
|
|
951
|
+
else:
|
|
952
|
+
print("可用模板:"); [print(f" {k}: {d}") for k, d in rows]
|
|
953
|
+
return
|
|
954
|
+
|
|
955
|
+
if _tmpl_key in self._SCAFFOLD_TEMPLATES:
|
|
956
|
+
tmpl = self._SCAFFOLD_TEMPLATES[_tmpl_key]
|
|
957
|
+
# Optional second arg: target directory name
|
|
958
|
+
_args_parts = args.strip().split()
|
|
959
|
+
_target_name = _args_parts[1] if len(_args_parts) > 1 else f"{_tmpl_key}_project"
|
|
960
|
+
_target_path = pathlib.Path(_target_name).expanduser()
|
|
961
|
+
if _target_path.is_absolute():
|
|
962
|
+
target_dir = _target_path
|
|
963
|
+
else:
|
|
964
|
+
from aria_code.artifacts import user_projects_dir as _user_projects_dir
|
|
965
|
+
target_dir = _user_projects_dir() / _target_name
|
|
966
|
+
if target_dir.exists():
|
|
967
|
+
self.context.console.print(f"[yellow]目录已存在: {target_dir}[/yellow]") if self.context.has_rich else print(f"目录已存在: {target_dir}")
|
|
968
|
+
else:
|
|
969
|
+
target_dir.mkdir(parents=True)
|
|
970
|
+
created = self._create_scaffold(target_dir, tmpl)
|
|
971
|
+
if self.context.has_rich:
|
|
972
|
+
from rich.panel import Panel as _SPanel
|
|
973
|
+
from rich import box as _sbox
|
|
974
|
+
lines = "\n".join(f" [dim]{pathlib.Path(p)}[/dim]" for p in created)
|
|
975
|
+
self.context.console.print(_SPanel(
|
|
976
|
+
f"[green]✅ 项目脚手架已创建[/green] [bold]{_target_name}[/bold]\n\n{lines}\n\n"
|
|
977
|
+
f"[dim]cd \"{target_dir}\" && pip install -r requirements.txt[/dim]",
|
|
978
|
+
title=f"[bold cyan]/init {_tmpl_key}[/bold cyan]",
|
|
979
|
+
border_style="cyan",
|
|
980
|
+
box=_sbox.ROUNDED,
|
|
981
|
+
padding=(1, 2),
|
|
982
|
+
))
|
|
983
|
+
else:
|
|
984
|
+
print(f"✅ 创建: {target_dir}"); [print(f" {p}") for p in created]
|
|
985
|
+
# Optionally generate ARIA.md inside the new project
|
|
986
|
+
try:
|
|
987
|
+
from aria_code.apps.cli.project_aria import build_project_aria_md
|
|
988
|
+
_aria_tgt = target_dir / "ARIA.md"
|
|
989
|
+
if not _aria_tgt.exists():
|
|
990
|
+
_aria_tgt.write_text(
|
|
991
|
+
build_project_aria_md(
|
|
992
|
+
project_name=_target_name,
|
|
993
|
+
stack=tmpl["desc"],
|
|
994
|
+
entry="main.py",
|
|
995
|
+
purpose=f"由 /init {_tmpl_key} 生成的脚手架项目",
|
|
996
|
+
notes=[
|
|
997
|
+
"脚手架目录已经就绪,后续功能与服务应继续按模块拆分。",
|
|
998
|
+
],
|
|
999
|
+
),
|
|
1000
|
+
encoding="utf-8",
|
|
1001
|
+
)
|
|
1002
|
+
except Exception:
|
|
1003
|
+
pass
|
|
1004
|
+
return
|
|
1005
|
+
# ── /init [--force] — generate ARIA.md for current project ──────────
|
|
1006
|
+
|
|
1007
|
+
aria_md = cwd / "ARIA.md"
|
|
1008
|
+
force = "--force" in args
|
|
1009
|
+
|
|
1010
|
+
if aria_md.exists() and not force:
|
|
1011
|
+
msg = "ARIA.md already exists. Use /init --force to regenerate."
|
|
1012
|
+
self.context.console.print(f"[yellow]{msg}[/yellow]") if self.context.has_rich else print(msg)
|
|
1013
|
+
return
|
|
1014
|
+
|
|
1015
|
+
# Scan for common project signal files
|
|
1016
|
+
_SCAN_FILES = [
|
|
1017
|
+
"README.md", "README.rst", "README.txt",
|
|
1018
|
+
"package.json", "pyproject.toml", "setup.py", "setup.cfg",
|
|
1019
|
+
"requirements.txt", "Pipfile", "poetry.lock",
|
|
1020
|
+
"Cargo.toml", "go.mod", "pom.xml", "build.gradle",
|
|
1021
|
+
"Makefile", "Dockerfile", ".env.example",
|
|
1022
|
+
"CLAUDE.md", ".ariarc",
|
|
1023
|
+
]
|
|
1024
|
+
snippets, found_files = [], []
|
|
1025
|
+
for fname in _SCAN_FILES:
|
|
1026
|
+
fp = cwd / fname
|
|
1027
|
+
if fp.exists():
|
|
1028
|
+
found_files.append(fname)
|
|
1029
|
+
try:
|
|
1030
|
+
snippets.append(f"### {fname}\n{fp.read_text(errors='replace')[:1200]}")
|
|
1031
|
+
except Exception:
|
|
1032
|
+
pass
|
|
1033
|
+
|
|
1034
|
+
code_exts = {".py", ".ts", ".js", ".go", ".rs", ".java", ".cpp", ".c"}
|
|
1035
|
+
code_files = sorted(
|
|
1036
|
+
f.name for f in cwd.iterdir()
|
|
1037
|
+
if f.is_file() and f.suffix in code_exts
|
|
1038
|
+
)[:10]
|
|
1039
|
+
|
|
1040
|
+
scan_summary = "\n\n".join(snippets[:5])
|
|
1041
|
+
|
|
1042
|
+
prompt = (
|
|
1043
|
+
f"分析以下项目信息,生成一个 ARIA.md 记忆文件。\n\n"
|
|
1044
|
+
f"目录: {cwd}\n"
|
|
1045
|
+
f"发现的配置文件: {', '.join(found_files) or '无'}\n"
|
|
1046
|
+
f"代码文件: {', '.join(code_files) or '无'}\n\n"
|
|
1047
|
+
f"文件内容:\n{scan_summary}\n\n"
|
|
1048
|
+
f"请生成符合以下格式的 ARIA.md(只输出文件内容本身,不加任何解释):\n\n"
|
|
1049
|
+
f"# Memory\n\n"
|
|
1050
|
+
f"- **Project**: <项目名称>\n"
|
|
1051
|
+
f"- **Stack**: <语言/框架>\n"
|
|
1052
|
+
f"- **Entry**: <主入口文件>\n"
|
|
1053
|
+
f"- **Conventions**: <代码规范或约定>\n"
|
|
1054
|
+
f"- **Notes**: <其他重要信息>\n"
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
self.context.console.print("[dim]分析项目结构中...[/dim]") if self.context.has_rich else print("Analyzing project...")
|
|
1058
|
+
await self.terminal.send_message(prompt)
|
|
1059
|
+
|
|
1060
|
+
# Extract the last assistant response and write to ARIA.md
|
|
1061
|
+
if self.terminal.conversation:
|
|
1062
|
+
last_ai = next(
|
|
1063
|
+
(m["content"] for m in reversed(self.terminal.conversation)
|
|
1064
|
+
if m.get("role") == "assistant"),
|
|
1065
|
+
None,
|
|
1066
|
+
)
|
|
1067
|
+
if last_ai:
|
|
1068
|
+
content = _strip_markdown_fences(last_ai).strip()
|
|
1069
|
+
# Strip injected market-data blocks (lines starting with ## 📊 or *⚠️*)
|
|
1070
|
+
# that the market-data prefetch may have appended to the AI response.
|
|
1071
|
+
import re as _re_init
|
|
1072
|
+
content = _re_init.sub(
|
|
1073
|
+
r'\n*## 📊.*?(?=\n#|\Z)', '', content, flags=_re_init.DOTALL
|
|
1074
|
+
).strip()
|
|
1075
|
+
content = _re_init.sub(r'\n*\*⚠️.*?\*\n*', '\n', content).strip()
|
|
1076
|
+
if not content.startswith("# Memory"):
|
|
1077
|
+
content = "# Memory\n\n" + content
|
|
1078
|
+
aria_md.write_text(content + "\n", encoding="utf-8")
|
|
1079
|
+
_PROJECT_CONTEXT = _load_project_context()
|
|
1080
|
+
msg = f"ARIA.md created at {aria_md}"
|
|
1081
|
+
self.context.console.print(f"\n[green]{msg}[/green]") if self.context.has_rich else print(f"\n{msg}")
|
|
1082
|
+
|
|
1083
|
+
async def cmd_setup(self, args: str):
|
|
1084
|
+
"""Guided first-run setup wizard (Open Interpreter style).
|
|
1085
|
+
|
|
1086
|
+
Usage: /setup
|
|
1087
|
+
"""
|
|
1088
|
+
import getpass as _gp
|
|
1089
|
+
|
|
1090
|
+
_is_interactive = sys.stdin.isatty()
|
|
1091
|
+
|
|
1092
|
+
if self.context.has_rich:
|
|
1093
|
+
self.context.console.print()
|
|
1094
|
+
self.context.console.print("[bold cyan]━━ Aria Setup Wizard ━━━━━━━━━━━━━━━━━━━━━━━━━━━[/bold cyan]")
|
|
1095
|
+
self.context.console.print()
|
|
1096
|
+
|
|
1097
|
+
# ── Step 1: Detect LOCAL backends only (not cloud LLM providers) ───────
|
|
1098
|
+
_LOCAL_BACKENDS_ONLY = {"ollama", "lmstudio", "vllm", "llamacpp", "jan"}
|
|
1099
|
+
try:
|
|
1100
|
+
from aria_code.local_llm_provider import probe_all_backends, BACKEND_DEFAULTS
|
|
1101
|
+
_all_backends = probe_all_backends()
|
|
1102
|
+
# Filter to only true local backends — cloud providers appear in Step 3
|
|
1103
|
+
backends = {k: v for k, v in _all_backends.items() if k in _LOCAL_BACKENDS_ONLY}
|
|
1104
|
+
except ImportError:
|
|
1105
|
+
backends = {}
|
|
1106
|
+
|
|
1107
|
+
self.context.console.print(" [bold]Step 1/4 · 本地 Backend[/bold]") if self.context.has_rich else print("Step 1: Local Backends")
|
|
1108
|
+
ollama_online = backends.get("ollama", False)
|
|
1109
|
+
for name, ok in backends.items():
|
|
1110
|
+
icon = "✅" if ok else "○"
|
|
1111
|
+
color = "green" if ok else "dim"
|
|
1112
|
+
url = BACKEND_DEFAULTS.get(name, {}).get("default_url", "") if "BACKEND_DEFAULTS" in dir() else ""
|
|
1113
|
+
if self.context.has_rich:
|
|
1114
|
+
self.context.console.print(f" {icon} [{color}]{name:12s}[/{color}] [dim]{url}[/dim]")
|
|
1115
|
+
else:
|
|
1116
|
+
print(f" {'✓' if ok else '✗'} {name:12s} {url}")
|
|
1117
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1118
|
+
|
|
1119
|
+
# ── Step 2: Pick default Ollama model (if Ollama online) ────────────
|
|
1120
|
+
if ollama_online and _is_interactive:
|
|
1121
|
+
self.context.console.print(" [bold]Step 2/4 · 选择默认本地模型[/bold]") if self.context.has_rich else print("Step 2: Default model")
|
|
1122
|
+
rich_models, _ = detect_ollama_models_rich(
|
|
1123
|
+
self.terminal.config.get("ollama_url", "http://localhost:11434")
|
|
1124
|
+
)
|
|
1125
|
+
if rich_models:
|
|
1126
|
+
model_names = [m["name"] for m in rich_models]
|
|
1127
|
+
current_id = self.terminal.config.get("model", "")
|
|
1128
|
+
sel_idx = next((i for i, n in enumerate(model_names) if n == current_id), 0)
|
|
1129
|
+
options = [(f" {n}", "") for n in model_names]
|
|
1130
|
+
picked = _arrow_select(options, sel_idx, "选择默认模型")
|
|
1131
|
+
if picked is not None:
|
|
1132
|
+
chosen = model_names[picked]
|
|
1133
|
+
self.terminal.config["model"] = chosen
|
|
1134
|
+
self.context.save_config(self.terminal.config)
|
|
1135
|
+
msg = f"✓ 默认模型设为 {chosen}"
|
|
1136
|
+
self.context.console.print(f" [green]{msg}[/green]") if self.context.has_rich else print(f" {msg}")
|
|
1137
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1138
|
+
else:
|
|
1139
|
+
self.context.console.print(" [dim]Step 2/4 · (Ollama 未运行,跳过模型选择)[/dim]") if self.context.has_rich else print(" Skipping model select (Ollama offline)")
|
|
1140
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1141
|
+
|
|
1142
|
+
# ── Step 3: Cloud API keys ───────────────────────────────────────────
|
|
1143
|
+
self.context.console.print(" [bold]Step 3/4 · Cloud API Key 配置[/bold]") if self.context.has_rich else print("Step 3: Cloud API Keys")
|
|
1144
|
+
_SETUP_PROVIDERS = [
|
|
1145
|
+
("deepseek", "DeepSeek", "推荐:deepseek-chat,性价比最高"),
|
|
1146
|
+
("openai", "OpenAI", "GPT-4o,o1等"),
|
|
1147
|
+
("groq", "Groq", "免费 llama/mixtral 推理,极速"),
|
|
1148
|
+
("anthropic", "Anthropic", "Claude 3.5/3.7"),
|
|
1149
|
+
]
|
|
1150
|
+
for prov, label, desc in _SETUP_PROVIDERS:
|
|
1151
|
+
existing_key = _get_provider_key(prov)
|
|
1152
|
+
if existing_key:
|
|
1153
|
+
masked = existing_key[:6] + "****" + existing_key[-4:]
|
|
1154
|
+
self.context.console.print(f" 🔑 {label:12s} [dim]已配置 ({masked})[/dim]") if self.context.has_rich else print(f" {label}: 已配置")
|
|
1155
|
+
continue
|
|
1156
|
+
if _is_interactive:
|
|
1157
|
+
self.context.console.print(f" [cyan]{label}[/cyan] [dim]({desc})[/dim]") if self.context.has_rich else print(f" {label}: {desc}")
|
|
1158
|
+
try:
|
|
1159
|
+
key = _gp.getpass(f" Enter {label} API key (留空跳过): ").strip()
|
|
1160
|
+
except Exception:
|
|
1161
|
+
key = ""
|
|
1162
|
+
if key:
|
|
1163
|
+
self.cmd_apikey(f"set {prov} {key}")
|
|
1164
|
+
else:
|
|
1165
|
+
self.context.console.print(f" ○ {label:12s} [dim]未配置 → /apikey set {prov} <key>[/dim]") if self.context.has_rich else print(f" {label}: not configured")
|
|
1166
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1167
|
+
|
|
1168
|
+
# ── Step 3.5: Data Service API keys ──────────────────────────────────
|
|
1169
|
+
self.context.console.print(" [bold]Step 3.5/4 · 市场数据服务 Key(后端离线时使用)[/bold]") if self.context.has_rich else print("Step 3.5: Data Service Keys")
|
|
1170
|
+
_SETUP_DATA = [
|
|
1171
|
+
("finnhub", "Finnhub", "股票实时行情+新闻", "https://finnhub.io/register"),
|
|
1172
|
+
("newsapi", "NewsAPI", "财经新闻聚合", "https://newsapi.org/register"),
|
|
1173
|
+
("brave", "Brave Search", "网页搜索", "https://api.search.brave.com/app/keys"),
|
|
1174
|
+
("alphavantage", "Alpha Vantage", "股票历史数据", "https://www.alphavantage.co/support/#api-key"),
|
|
1175
|
+
]
|
|
1176
|
+
_existing_data = _load_data_keys()
|
|
1177
|
+
for svc, label, desc, signup_url in _SETUP_DATA:
|
|
1178
|
+
existing_key = _existing_data.get(svc, "")
|
|
1179
|
+
if existing_key:
|
|
1180
|
+
masked = existing_key[:6] + "****" + existing_key[-4:]
|
|
1181
|
+
self.context.console.print(f" 🔑 {label:16s} [dim]已配置 ({masked})[/dim]") if self.context.has_rich else print(f" {label}: configured")
|
|
1182
|
+
continue
|
|
1183
|
+
if _is_interactive:
|
|
1184
|
+
self.context.console.print(f" [cyan]{label}[/cyan] [dim]({desc})[/dim]") if self.context.has_rich else print(f" {label}: {desc}")
|
|
1185
|
+
self.context.console.print(f" [dim]注册:{signup_url}[/dim]") if self.context.has_rich else print(f" Register: {signup_url}")
|
|
1186
|
+
try:
|
|
1187
|
+
key = _gp.getpass(f" Enter {label} API key (留空跳过): ").strip()
|
|
1188
|
+
except Exception:
|
|
1189
|
+
key = ""
|
|
1190
|
+
if key:
|
|
1191
|
+
self.cmd_apikey(f"set {svc} {key}")
|
|
1192
|
+
else:
|
|
1193
|
+
if self.context.has_rich:
|
|
1194
|
+
self.context.console.print(f" ○ {label:16s} [dim]未配置 → /apikey set {svc} <key>[/dim]")
|
|
1195
|
+
self.context.console.print(f" [dim]注册:{signup_url}[/dim]")
|
|
1196
|
+
else:
|
|
1197
|
+
print(f" {label}: not configured → /apikey set {svc} <key>")
|
|
1198
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1199
|
+
|
|
1200
|
+
# ── Step 3.8: MCP servers ────────────────────────────────────────────
|
|
1201
|
+
sub = args.strip().lower()
|
|
1202
|
+
if sub in ("mcp", "all"):
|
|
1203
|
+
self.context.console.print(" [bold]Step 3.8/4 · MCP 服务器[/bold]") if self.context.has_rich else print("Step 3.8: MCP Servers")
|
|
1204
|
+
_mcp_cfg_path = aria_home() / "mcp_servers.json"
|
|
1205
|
+
if _mcp_cfg_path.exists():
|
|
1206
|
+
try:
|
|
1207
|
+
import json as _j2
|
|
1208
|
+
_mcp_data = _j2.loads(_mcp_cfg_path.read_text())
|
|
1209
|
+
_servers = _mcp_data.get("servers", [])
|
|
1210
|
+
enabled_srv = [s for s in _servers if s.get("enabled", False)]
|
|
1211
|
+
disabled_srv = [s for s in _servers if not s.get("enabled", True)]
|
|
1212
|
+
for s in enabled_srv:
|
|
1213
|
+
self.context.console.print(f" ✅ {s['name']:16s} [dim]{s.get('description','')[:50]}[/dim]") if self.context.has_rich else print(f" ✓ {s['name']}")
|
|
1214
|
+
for s in disabled_srv:
|
|
1215
|
+
note = s.get("_setup", "")
|
|
1216
|
+
self.context.console.print(f" ○ {s['name']:16s} [dim]{s.get('description','')[:50]}[/dim]") if self.context.has_rich else print(f" ✗ {s['name']}")
|
|
1217
|
+
if note:
|
|
1218
|
+
self.context.console.print(f" [dim]安装: {note}[/dim]") if self.context.has_rich else print(f" Setup: {note}")
|
|
1219
|
+
if disabled_srv:
|
|
1220
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1221
|
+
self.context.console.print(" [dim]安装后编辑 ~/.arthera/mcp_servers.json 将对应项 enabled 改为 true[/dim]") if self.context.has_rich else print(" Edit mcp_servers.json: set enabled=true after installing")
|
|
1222
|
+
except Exception:
|
|
1223
|
+
pass
|
|
1224
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1225
|
+
|
|
1226
|
+
# ── Step 4: Messaging channels (Feishu / Telegram) ──────────────────
|
|
1227
|
+
if sub in ("feishu", "telegram", "notify", "all", ""):
|
|
1228
|
+
self.context.console.print(" [bold]Step 4/5 · 消息通知连接[/bold]") if self.context.has_rich else print("Step 4: Messaging")
|
|
1229
|
+
_env_path = pathlib.Path.home() / ".aria" / ".env"
|
|
1230
|
+
_env_vars: dict = {}
|
|
1231
|
+
if _env_path.exists():
|
|
1232
|
+
for _line in _env_path.read_text().splitlines():
|
|
1233
|
+
if "=" in _line and not _line.startswith("#"):
|
|
1234
|
+
k, _, v = _line.partition("=")
|
|
1235
|
+
_env_vars[k.strip()] = v.strip()
|
|
1236
|
+
|
|
1237
|
+
# Feishu status
|
|
1238
|
+
_fs_mode = _env_vars.get("ARIA_RELAY_MODE", "")
|
|
1239
|
+
_fs_id = _env_vars.get("ARIA_RELAY_CLIENT_ID", "")
|
|
1240
|
+
_fs_app = _env_vars.get("FEISHU_APP_ID", "")
|
|
1241
|
+
if _fs_mode == "relay" and _fs_id:
|
|
1242
|
+
_fs_status = f"[green]✓ 中继模式[/green] ID: {_fs_id[:12]}…"
|
|
1243
|
+
elif _fs_mode == "own_app" and _fs_app:
|
|
1244
|
+
_fs_status = f"[green]✓ 自建应用[/green] {_fs_app}"
|
|
1245
|
+
else:
|
|
1246
|
+
_fs_status = "[dim]未配置[/dim] → /setup feishu"
|
|
1247
|
+
|
|
1248
|
+
# Telegram status
|
|
1249
|
+
_tg_token = _env_vars.get("TELEGRAM_BOT_TOKEN", "")
|
|
1250
|
+
_tg_ids = _env_vars.get("TELEGRAM_ALLOWED_IDS", "")
|
|
1251
|
+
if _tg_token and _tg_token != "your_bot_token_here":
|
|
1252
|
+
_tg_status = f"[green]✓ 已配置[/green] Chat IDs: {_tg_ids or '(未设置)'}"
|
|
1253
|
+
else:
|
|
1254
|
+
_tg_status = "[dim]未配置[/dim] → /setup telegram"
|
|
1255
|
+
|
|
1256
|
+
if self.context.has_rich:
|
|
1257
|
+
self.context.console.print(f" 飞书 {_fs_status}")
|
|
1258
|
+
self.context.console.print(f" Telegram {_tg_status}")
|
|
1259
|
+
else:
|
|
1260
|
+
print(f" Feishu: {_fs_mode or 'not configured'}")
|
|
1261
|
+
print(f" Telegram: {'configured' if _tg_token else 'not configured'}")
|
|
1262
|
+
|
|
1263
|
+
# Sub-command: launch wizard for just this channel
|
|
1264
|
+
if sub in ("feishu", "telegram"):
|
|
1265
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1266
|
+
try:
|
|
1267
|
+
# setup_wizard 是随包发布的顶层模块(见 pyproject py-modules),
|
|
1268
|
+
# 直接 import 即可。原先用 __file__ 往上数四层拼路径:这个方法
|
|
1269
|
+
# 的 __globals__ 被 _rebind_mixin_globals 换成了 aria_cli 的,
|
|
1270
|
+
# __file__ 因此指向仓库根的 aria_cli.py 而非本文件,四层 parent
|
|
1271
|
+
# 算出 /Users/setup_wizard.py,永远不存在——异常被下面的
|
|
1272
|
+
# except 吞掉,这条分支一直静默退化成"请手动运行"提示。
|
|
1273
|
+
from aria_code import setup_wizard as _wiz
|
|
1274
|
+
_e = _wiz._load_env()
|
|
1275
|
+
if sub == "feishu":
|
|
1276
|
+
_wiz.setup_feishu(_e)
|
|
1277
|
+
else:
|
|
1278
|
+
_wiz.setup_telegram(_e)
|
|
1279
|
+
_wiz._save_env(_e)
|
|
1280
|
+
except Exception as _we:
|
|
1281
|
+
_fallback_flag = "--feishu" if sub == "feishu" else "--telegram"
|
|
1282
|
+
if self.context.has_rich:
|
|
1283
|
+
self.context.console.print(f" [yellow]请运行: python3 -m aria_code.setup_wizard {_fallback_flag}[/yellow]")
|
|
1284
|
+
else:
|
|
1285
|
+
print(f" Run: python3 -m aria_code.setup_wizard {_fallback_flag}")
|
|
1286
|
+
return
|
|
1287
|
+
|
|
1288
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1289
|
+
|
|
1290
|
+
# ── Step 5: Summary ─────────────────────────────────────────────────
|
|
1291
|
+
self.context.console.print(" [bold]Step 5/5 · 配置完成[/bold]") if self.context.has_rich else print("Step 5: Done")
|
|
1292
|
+
model = self.terminal.config.get("model", "?")
|
|
1293
|
+
provider = self.terminal.config.get("local_provider", "ollama")
|
|
1294
|
+
self.context.console.print(f" 模型: [cyan]{model}[/cyan] Provider: [cyan]{provider}[/cyan]") if self.context.has_rich else print(f" Model: {model} Provider: {provider}")
|
|
1295
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1296
|
+
self.context.console.print(
|
|
1297
|
+
" [dim]提示: /model — 切换模型 /providers — 查看所有 provider\n"
|
|
1298
|
+
" /setup feishu — 配置飞书 /setup telegram — 配置 Telegram[/dim]"
|
|
1299
|
+
) if self.context.has_rich else print(" Tip: /model /providers /setup feishu /setup telegram")
|
|
1300
|
+
self.context.console.print("[bold cyan]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/bold cyan]") if self.context.has_rich else print("─" * 50)
|
|
1301
|
+
self.context.console.print() if self.context.has_rich else print()
|
|
1302
|
+
|
|
1303
|
+
def cmd_memory(self, args: str):
|
|
1304
|
+
"""Manage persistent memory: project ARIA.md and global user profile.
|
|
1305
|
+
|
|
1306
|
+
Usage:
|
|
1307
|
+
/memory show — display current project ARIA.md
|
|
1308
|
+
/memory add <fact> — append fact to project ARIA.md
|
|
1309
|
+
/memory clear — wipe project ARIA.md memory section
|
|
1310
|
+
/memory search <query> — search across ARIA.md + sessions
|
|
1311
|
+
/memory profile — show global 当前账户的 ARIA.md (injected every session)
|
|
1312
|
+
/memory profile add <text> — append to global profile
|
|
1313
|
+
/memory profile clear — reset global profile
|
|
1314
|
+
/memory global — legacy global Memory entries
|
|
1315
|
+
"""
|
|
1316
|
+
global _PROJECT_CONTEXT
|
|
1317
|
+
aria_md = pathlib.Path.cwd() / "ARIA.md"
|
|
1318
|
+
parts = args.strip().split(maxsplit=1)
|
|
1319
|
+
sub = parts[0].lower() if parts else "show"
|
|
1320
|
+
rest = parts[1].strip() if len(parts) > 1 else ""
|
|
1321
|
+
|
|
1322
|
+
if sub in {"sync", "status", "account", "forget"}:
|
|
1323
|
+
from aria_code.account_memory import AccountMemory
|
|
1324
|
+
memory = AccountMemory(self.terminal.config, save=self.context.save_config)
|
|
1325
|
+
if sub == "sync":
|
|
1326
|
+
result = memory.sync(force=True)
|
|
1327
|
+
message = "账户记忆已同步。" if result.get("ok") else "账户记忆未同步:请检查登录状态或网络,待同步内容仍保留。"
|
|
1328
|
+
if result.get("ok") and result.get("pending"):
|
|
1329
|
+
message = f"本次同步已完成,仍有 {result['pending']} 条待同步。"
|
|
1330
|
+
if result.get("enabled") is False:
|
|
1331
|
+
message = "账户记忆已关闭,未上传或使用记忆。"
|
|
1332
|
+
elif sub == "forget":
|
|
1333
|
+
message = "已标记删除,下次同步会更新云端。" if memory.forget(rest) else "请先运行 /login。"
|
|
1334
|
+
elif sub == "account":
|
|
1335
|
+
if rest.startswith("add "):
|
|
1336
|
+
from aria_code.memory_manager import _SENSITIVE_PATTERN
|
|
1337
|
+
fact = rest[4:].strip()
|
|
1338
|
+
saved = bool(fact) and not _SENSITIVE_PATTERN.search(fact) and memory.add(fact)
|
|
1339
|
+
message = "已保存到当前账户的待同步记忆。" if saved else "未保存:请检查登录、记忆开关或内容。"
|
|
1340
|
+
else:
|
|
1341
|
+
facts = memory.facts()
|
|
1342
|
+
message = "\n".join(f"{f['id']} {f['content']}" for f in facts) or "当前账户暂无记忆。"
|
|
1343
|
+
else:
|
|
1344
|
+
status = memory.status()
|
|
1345
|
+
sync_label = {"sign-in-required":"需要重新登录", "service-unavailable":"服务暂不可用,请稍后重试", "pending-retry":"部分记忆待重试"}.get(status['error'], '部分记忆待同步' if status['pending'] else ('已同步' if status['last_synced'] else '尚未同步'))
|
|
1346
|
+
message = (f"账户:{status['email'] or ('已登录账户' if status['account_id'] else '未登录')}\n"
|
|
1347
|
+
f"记忆:{'开启' if status['enabled'] else '关闭'} · 待同步:{status['pending']}\n"
|
|
1348
|
+
f"同步状态:{sync_label}")
|
|
1349
|
+
# Memory is user-authored content, never interpret Rich markup in it.
|
|
1350
|
+
print(message)
|
|
1351
|
+
return
|
|
1352
|
+
|
|
1353
|
+
if sub == "show":
|
|
1354
|
+
if not aria_md.exists():
|
|
1355
|
+
msg = f"No ARIA.md in {pathlib.Path.cwd()}"
|
|
1356
|
+
self.context.console.print(f"[dim]{msg}[/dim]") if self.context.has_rich else print(msg)
|
|
1357
|
+
return
|
|
1358
|
+
content = aria_md.read_text(encoding="utf-8")
|
|
1359
|
+
if self.context.has_rich:
|
|
1360
|
+
try:
|
|
1361
|
+
from rich.markdown import Markdown as _RMd
|
|
1362
|
+
self.context.console.print(_RMd(content))
|
|
1363
|
+
except Exception:
|
|
1364
|
+
self.context.console.print(content)
|
|
1365
|
+
else:
|
|
1366
|
+
print(content)
|
|
1367
|
+
|
|
1368
|
+
elif sub == "add":
|
|
1369
|
+
if not rest:
|
|
1370
|
+
self.context.console.print("[dim]Usage: /memory add <fact>[/dim]") if self.context.has_rich else print("Usage: /memory add <fact>")
|
|
1371
|
+
return
|
|
1372
|
+
self.cmd_note(rest)
|
|
1373
|
+
|
|
1374
|
+
elif sub == "clear":
|
|
1375
|
+
if aria_md.exists():
|
|
1376
|
+
aria_md.write_text("# Memory\n\n", encoding="utf-8")
|
|
1377
|
+
_PROJECT_CONTEXT = _load_project_context()
|
|
1378
|
+
self.context.console.print("[dim]Memory cleared.[/dim]") if self.context.has_rich else print("Memory cleared.")
|
|
1379
|
+
else:
|
|
1380
|
+
self.context.console.print("[dim]Nothing to clear.[/dim]") if self.context.has_rich else print("Nothing to clear.")
|
|
1381
|
+
|
|
1382
|
+
elif sub == "search":
|
|
1383
|
+
# Semantic search in ARIA.md and strategy vault using simple grep
|
|
1384
|
+
# (ChromaDB RAG upgrade planned for Phase 2)
|
|
1385
|
+
if not rest:
|
|
1386
|
+
self.context.console.print("[dim]Usage: /memory search <query>[/dim]") if self.context.has_rich else print("Usage: /memory search <query>")
|
|
1387
|
+
return
|
|
1388
|
+
query_low = rest.lower()
|
|
1389
|
+
results = []
|
|
1390
|
+
# 1. Search ARIA.md
|
|
1391
|
+
if aria_md.exists():
|
|
1392
|
+
for line in aria_md.read_text(encoding="utf-8").splitlines():
|
|
1393
|
+
if query_low in line.lower() and line.strip():
|
|
1394
|
+
results.append(("ARIA.md", line.strip()))
|
|
1395
|
+
# 2. Search session history titles
|
|
1396
|
+
for sess_file in sorted(_get_SESSIONS_DIR().glob("*.json"), key=lambda p: -p.stat().st_mtime)[:20]:
|
|
1397
|
+
try:
|
|
1398
|
+
sess = json.loads(sess_file.read_text(encoding="utf-8"))
|
|
1399
|
+
title = sess.get("metadata", {}).get("title", "")
|
|
1400
|
+
if query_low in title.lower():
|
|
1401
|
+
results.append(("Session", title[:80]))
|
|
1402
|
+
except Exception:
|
|
1403
|
+
pass
|
|
1404
|
+
# 3. Search strategy vault
|
|
1405
|
+
try:
|
|
1406
|
+
from aria_code.strategy_vault import get_vault as _gv
|
|
1407
|
+
vault = _gv()
|
|
1408
|
+
for s in (vault.list() or []):
|
|
1409
|
+
name = str(s.get("name", ""))
|
|
1410
|
+
msg = str(s.get("message", ""))
|
|
1411
|
+
if query_low in name.lower() or query_low in msg.lower():
|
|
1412
|
+
results.append(("Strategy", f"{name}: {msg[:60]}"))
|
|
1413
|
+
except Exception:
|
|
1414
|
+
pass
|
|
1415
|
+
|
|
1416
|
+
if results:
|
|
1417
|
+
if self.context.has_rich:
|
|
1418
|
+
self.context.console.print()
|
|
1419
|
+
self.context.console.print(f" [bold]记忆搜索: '{rest}'[/bold] [dim]{len(results)} 条结果[/dim]")
|
|
1420
|
+
self.context.console.print()
|
|
1421
|
+
for src, text in results[:15]:
|
|
1422
|
+
self.context.console.print(f" [dim]{src:<12s}[/dim] {text}")
|
|
1423
|
+
self.context.console.print()
|
|
1424
|
+
else:
|
|
1425
|
+
print(f" Search '{rest}': {len(results)} results")
|
|
1426
|
+
for src, text in results[:15]:
|
|
1427
|
+
print(f" [{src}] {text}")
|
|
1428
|
+
else:
|
|
1429
|
+
msg = f"未找到与 '{rest}' 相关的记忆"
|
|
1430
|
+
self.context.console.print(f"[dim]{msg}[/dim]") if self.context.has_rich else print(msg)
|
|
1431
|
+
|
|
1432
|
+
elif sub == "profile":
|
|
1433
|
+
# Per-user ARIA.md at 当前账户的 ARIA.md — injected into every session
|
|
1434
|
+
from aria_code.account_session import profile_path
|
|
1435
|
+
_profile_path = profile_path(self.terminal.config)
|
|
1436
|
+
gparts = rest.strip().split(maxsplit=1)
|
|
1437
|
+
gsub = gparts[0].lower() if gparts else "show"
|
|
1438
|
+
grest = gparts[1].strip() if len(gparts) > 1 else ""
|
|
1439
|
+
|
|
1440
|
+
if gsub == "show":
|
|
1441
|
+
if not _profile_path.exists():
|
|
1442
|
+
if self.context.has_rich:
|
|
1443
|
+
self.context.console.print("[dim]当前账户的 ARIA.md 还不存在。用 /memory profile add <内容> 创建。[/dim]")
|
|
1444
|
+
else:
|
|
1445
|
+
print("当前账户的 ARIA.md not found. Use /memory profile add <text> to create.")
|
|
1446
|
+
return
|
|
1447
|
+
content = _profile_path.read_text(encoding="utf-8")
|
|
1448
|
+
if self.context.has_rich:
|
|
1449
|
+
try:
|
|
1450
|
+
from rich.markdown import Markdown as _RMd3
|
|
1451
|
+
self.context.console.print()
|
|
1452
|
+
self.context.console.print(" [dim]当前账户的 ARIA.md[/dim]")
|
|
1453
|
+
self.context.console.print(_RMd3(content))
|
|
1454
|
+
except Exception:
|
|
1455
|
+
self.context.console.print(content)
|
|
1456
|
+
else:
|
|
1457
|
+
print(content)
|
|
1458
|
+
|
|
1459
|
+
elif gsub == "add":
|
|
1460
|
+
if not grest:
|
|
1461
|
+
self.context.console.print("[dim]Usage: /memory profile add <内容>[/dim]") if self.context.has_rich else print("Usage: /memory profile add <text>")
|
|
1462
|
+
return
|
|
1463
|
+
_profile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1464
|
+
now_str = datetime.now().strftime("%Y-%m-%d")
|
|
1465
|
+
if _profile_path.exists():
|
|
1466
|
+
existing = _profile_path.read_text(encoding="utf-8")
|
|
1467
|
+
if "## 偏好与背景" not in existing and "## Preferences" not in existing:
|
|
1468
|
+
existing += "\n\n## 偏好与背景\n"
|
|
1469
|
+
existing += f"\n- [{now_str}] {grest}"
|
|
1470
|
+
_profile_path.write_text(existing, encoding="utf-8")
|
|
1471
|
+
else:
|
|
1472
|
+
_profile_path.write_text(
|
|
1473
|
+
f"# 用户背景\n\n## 偏好与背景\n\n- [{now_str}] {grest}\n",
|
|
1474
|
+
encoding="utf-8",
|
|
1475
|
+
)
|
|
1476
|
+
from aria_code.account_memory import AccountMemory
|
|
1477
|
+
from aria_code.memory_manager import _SENSITIVE_PATTERN
|
|
1478
|
+
if not _SENSITIVE_PATTERN.search(grest):
|
|
1479
|
+
AccountMemory(self.terminal.config).add(grest)
|
|
1480
|
+
# Refresh project context so change takes effect immediately
|
|
1481
|
+
_PROJECT_CONTEXT = _load_project_context()
|
|
1482
|
+
if self.context.has_rich:
|
|
1483
|
+
self.context.console.print(" [dim]✓ 已写入 当前账户的 ARIA.md — 下次对话自动注入[/dim]")
|
|
1484
|
+
else:
|
|
1485
|
+
print("Saved to 当前账户的 ARIA.md")
|
|
1486
|
+
|
|
1487
|
+
elif gsub == "clear":
|
|
1488
|
+
if _profile_path.exists():
|
|
1489
|
+
_profile_path.write_text("# 用户背景\n\n", encoding="utf-8")
|
|
1490
|
+
_PROJECT_CONTEXT = _load_project_context()
|
|
1491
|
+
self.context.console.print("[dim]当前账户的 ARIA.md 已清空。[/dim]") if self.context.has_rich else print("Profile cleared.")
|
|
1492
|
+
else:
|
|
1493
|
+
self.context.console.print("[dim]文件不存在,无需清空。[/dim]") if self.context.has_rich else print("Nothing to clear.")
|
|
1494
|
+
|
|
1495
|
+
else:
|
|
1496
|
+
if self.context.has_rich:
|
|
1497
|
+
self.context.console.print("[dim]Usage: /memory profile [show|add <内容>|clear][/dim]")
|
|
1498
|
+
else:
|
|
1499
|
+
print("Usage: /memory profile [show|add <text>|clear]")
|
|
1500
|
+
|
|
1501
|
+
elif sub == "global":
|
|
1502
|
+
# Global user memory (cross-project, cross-session)
|
|
1503
|
+
if not self.memory_mgr:
|
|
1504
|
+
self.context.console.print("[dim]Memory manager not available.[/dim]") if self.context.has_rich else print("Memory manager not available.")
|
|
1505
|
+
return
|
|
1506
|
+
gparts = rest.strip().split(maxsplit=1)
|
|
1507
|
+
gsub = gparts[0].lower() if gparts else "show"
|
|
1508
|
+
grest = gparts[1].strip() if len(gparts) > 1 else ""
|
|
1509
|
+
|
|
1510
|
+
if gsub == "show":
|
|
1511
|
+
entries = self.memory_mgr.list_all()
|
|
1512
|
+
if not entries:
|
|
1513
|
+
self.context.console.print("[dim]全局 Memory 为空。用 /memory global add <内容> 添加。[/dim]") if self.context.has_rich else print("Global memory is empty.")
|
|
1514
|
+
return
|
|
1515
|
+
if self.context.has_rich:
|
|
1516
|
+
from rich.markdown import Markdown as _RMd2
|
|
1517
|
+
for e in entries:
|
|
1518
|
+
self.context.console.print(f"\n[bold cyan]{e['title']}[/bold cyan] [dim]{e['file']}[/dim]")
|
|
1519
|
+
self.context.console.print(_RMd2(e["content"]) if e["content"] else "[dim](empty)[/dim]")
|
|
1520
|
+
else:
|
|
1521
|
+
for e in entries:
|
|
1522
|
+
print(f"\n## {e['title']}\n{e['content']}")
|
|
1523
|
+
|
|
1524
|
+
elif gsub == "add":
|
|
1525
|
+
if not grest:
|
|
1526
|
+
self.context.console.print("[dim]Usage: /memory global add <内容>[/dim]") if self.context.has_rich else print("Usage: /memory global add <content>")
|
|
1527
|
+
return
|
|
1528
|
+
saved = self.memory_mgr.append("user_profile", grest, title="User Profile")
|
|
1529
|
+
print("已保存记忆;账户记忆将在下次对话或 /memory sync 时同步。" if saved is not False else "未保存:请检查记忆开关和内容。")
|
|
1530
|
+
|
|
1531
|
+
elif gsub == "clear":
|
|
1532
|
+
n = self.memory_mgr.clear_all()
|
|
1533
|
+
print(f"已清除当前记忆({n} 项);云端删除将在下次同步时完成。")
|
|
1534
|
+
|
|
1535
|
+
else:
|
|
1536
|
+
self.context.console.print("[dim]Usage: /memory global [show|add <内容>|clear][/dim]") if self.context.has_rich else print("Usage: /memory global [show|add|clear]")
|
|
1537
|
+
|
|
1538
|
+
else:
|
|
1539
|
+
if self.context.has_rich:
|
|
1540
|
+
self.context.console.print("[dim]Usage: /memory [show|add <fact>|clear|search <query>|profile|global][/dim]")
|
|
1541
|
+
self.context.console.print("[dim] /memory profile add <内容> — 写入全局用户背景(每次会话自动注入)[/dim]")
|
|
1542
|
+
else:
|
|
1543
|
+
print("Usage: /memory [show|add <fact>|clear|search <query>|profile|global]")
|