Coverage for agentos/cli/main.py: 4%

646 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 08:01 +0800

1""" 

2AgentOS v1.7.1 CLI — System layer + Desktop client: file ops, shell, browser, visual approval, native desktop shell。 

3""" 

4 

5from __future__ import annotations 

6 

7import asyncio 

8import os 

9import sys 

10 

11from agentos.llm.factory import create_provider 

12from agentos.llm.base import Tool, ToolParameter 

13from agentos.agent.tool_agent import ToolAgent, ToolExecutor, AgentConfig, MockLLMProvider 

14from agentos.cli.errors import ( 

15 no_provider_configured, 

16 single_provider_failed, 

17 no_task_provided, 

18 welcome, 

19) 

20 

21 

22def _build_executor() -> ToolExecutor: 

23 executor = ToolExecutor() 

24 

25 # Shell tool 

26 executor.register( 

27 Tool.from_function( 

28 name="run_shell", 

29 description="Execute a shell command. Commands run in a sandboxed temporary directory.", 

30 parameters={ 

31 "command": ToolParameter( 

32 type="string", 

33 description="The shell command to execute.", 

34 ), 

35 }, 

36 ), 

37 lambda command: _run_shell_unsafe(command), 

38 ) 

39 

40 # File tools 

41 executor.register( 

42 Tool.from_function( 

43 name="read_file", 

44 description="Read contents of a file at the given path.", 

45 parameters={ 

46 "file_path": ToolParameter( 

47 type="string", 

48 description="Absolute path to the file.", 

49 ), 

50 }, 

51 ), 

52 lambda file_path: _read_file(file_path), 

53 ) 

54 

55 executor.register( 

56 Tool.from_function( 

57 name="list_directory", 

58 description="List files and subdirectories in a directory.", 

59 parameters={ 

60 "path": ToolParameter( 

61 type="string", 

62 description="Absolute path to the directory.", 

63 ), 

64 }, 

65 ), 

66 lambda path: _list_directory(path), 

67 ) 

68 

69 executor.register( 

70 Tool.from_function( 

71 name="write_file", 

72 description="Write text content to a file.", 

73 parameters={ 

74 "file_path": ToolParameter( 

75 type="string", 

76 description="Absolute path to write to.", 

77 ), 

78 "content": ToolParameter( 

79 type="string", 

80 description="Text content to write.", 

81 ), 

82 }, 

83 ), 

84 lambda file_path, content: _write_file(file_path, content), 

85 ) 

86 

87 return executor 

88 

89 

90def _run_shell_unsafe(command: str) -> str: 

91 import subprocess 

92 import tempfile 

93 try: 

94 with tempfile.TemporaryDirectory() as td: 

95 result = subprocess.run( 

96 command, shell=True, capture_output=True, text=True, 

97 timeout=30, cwd=td, 

98 ) 

99 out = result.stdout.strip() 

100 err = result.stderr.strip() 

101 if err: 

102 return f"stdout:\n{out}\n\nstderr:\n{err}" 

103 return out or "(no output)" 

104 except subprocess.TimeoutExpired: 

105 return "Error: command timed out (30s)" 

106 except Exception as e: 

107 return f"Error: {e}" 

108 

109 

110def _read_file(file_path: str) -> str: 

111 try: 

112 with open(file_path) as f: 

113 return f.read() 

114 except Exception as e: 

115 return f"Error: {e}" 

116 

117 

118def _list_directory(path: str) -> str: 

119 try: 

120 entries = os.listdir(path) 

121 return "\n".join(sorted(entries)) or "(empty)" 

122 except Exception as e: 

123 return f"Error: {e}" 

124 

125 

126def _write_file(file_path: str, content: str) -> str: 

127 try: 

128 os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True) 

129 with open(file_path, "w") as f: 

130 f.write(content) 

131 return f"Written {len(content)} bytes to {file_path}" 

132 except Exception as e: 

133 return f"Error: {e}" 

134 

135 

136def _build_agent(provider_name: str = "", verbose: bool = False) -> ToolAgent: 

137 if provider_name: 

138 provider = create_provider(provider_name) 

139 elif os.getenv("OPENAI_API_KEY"): 

140 provider = create_provider("openai") 

141 provider_name = "openai" 

142 elif os.getenv("DEEPSEEK_API_KEY"): 

143 provider = create_provider("deepseek") 

144 provider_name = "deepseek" 

145 elif os.getenv("ANTHROPIC_API_KEY"): 

146 provider = create_provider("anthropic") 

147 provider_name = "anthropic" 

148 else: 

149 welcome() 

150 no_provider_configured() 

151 

152 config = AgentConfig(verbose=verbose) 

153 executor = _build_executor() 

154 

155 return ToolAgent( 

156 provider=provider, 

157 tool_executor=executor, 

158 config=config, 

159 system_prompt=( 

160 "你是 AgentOS 智能助手。你可以使用以下工具完成任务:\n" 

161 "- run_shell: 执行 Shell 命令\n" 

162 "- read_file: 读取文件内容\n" 

163 "- write_file: 写入文本到文件\n" 

164 "- list_directory: 列出目录内容\n\n" 

165 "当需要操作文件或执行命令时调用对应工具。" 

166 "获得足够信息后直接回答,不要再调用工具。" 

167 "使用中文回复。" 

168 ), 

169 ) 

170 

171 

172def _run_hello(): 

173 """一键 hello world 体验 — 无需配置,始终可用。""" 

174 from agentos import __version__ 

175 import time 

176 

177 print(f" \033[36mNexus AgentOS\033[0m v{__version__}") 

178 print(f" \033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m") 

179 print() 

180 

181 # Step 1: check provider 

182 provider_label = "Mock(演示模式)" 

183 provider_color = "\033[33m" 

184 if os.environ.get("OPENAI_API_KEY"): 

185 provider_label = "OpenAI (gpt-4o-mini)" 

186 provider_color = "\033[32m" 

187 elif os.environ.get("DEEPSEEK_API_KEY"): 

188 provider_label = "DeepSeek (deepseek-chat)" 

189 provider_color = "\033[32m" 

190 elif os.environ.get("ANTHROPIC_API_KEY"): 

191 provider_label = "Anthropic (claude-sonnet-4)" 

192 provider_color = "\033[32m" 

193 

194 steps = [ 

195 ("检测环境", f"{provider_color}{provider_label}\033[0m"), 

196 ("核心引擎", "\033[32mToolAgent 多步推理循环\033[0m"), 

197 ("工具系统", "\033[32mShell / 文件读写 / 目录浏览\033[0m"), 

198 ("安全护栏", "\033[32mGuardrails PII注入检测\033[0m"), 

199 ("可观测性", "\033[32mTracker + Dashboard SSE\033[0m"), 

200 ("MCP 协议", "\033[32mstdio JSON-RPC 2.0\033[0m"), 

201 ("RAG 管道", "\033[32mChroma/FAISS 向量检索\033[0m"), 

202 ("企业特性", "\033[32mRBAC/多租户/审计/API Key\033[0m"), 

203 ] 

204 

205 for label, status in steps: 

206 time.sleep(0.12) 

207 print(f" \033[2m▸\033[0m {label:<12s} {status}") 

208 

209 time.sleep(0.3) 

210 print() 

211 print(f" \033[1m一切就绪。\033[0m") 

212 print() 

213 

214 if os.environ.get("OPENAI_API_KEY") or os.environ.get("DEEPSEEK_API_KEY") or os.environ.get("ANTHROPIC_API_KEY"): 

215 print(f" 快速开始:") 

216 print(f" \033[32magentos\033[0m \"用一句话解释什么是递归\"") 

217 print(f" \033[32magentos demo\033[0m") 

218 else: 

219 print(f" 下一步(30 秒配置):") 

220 print(f" \033[32magentos init\033[0m 终端交互式配置") 

221 print(f" \033[32magentos config-panel\033[0m 浏览器图形界面") 

222 print() 

223 

224 

225def _run_file_demo(verbose: bool): 

226 """文件操作演示 — 创建、读取、列表。""" 

227 from agentos.agent.tool_agent import ToolAgent, ToolExecutor, AgentConfig 

228 

229 executor = ToolExecutor() 

230 

231 def write_file(path: str, content: str) -> str: 

232 import os as _os 

233 try: 

234 _os.makedirs(_os.path.dirname(path) or ".", exist_ok=True) 

235 with open(path, "w") as f: 

236 f.write(content) 

237 return f"已写入 {path} ({len(content)} 字节)" 

238 except Exception as e: 

239 return f"写入失败: {e}" 

240 

241 def read_file(path: str) -> str: 

242 try: 

243 with open(path) as f: 

244 return f.read() 

245 except Exception as e: 

246 return f"读取失败: {e}" 

247 

248 def list_dir(path: str) -> str: 

249 import os as _os 

250 try: 

251 return "\n".join(sorted(_os.listdir(path))) or "(空)" 

252 except Exception as e: 

253 return f"列出失败: {e}" 

254 

255 executor.register( 

256 Tool.from_function("write_file", "写入文本到文件", { 

257 "path": ToolParameter(type="string", description="文件路径"), 

258 "content": ToolParameter(type="string", description="文件内容"), 

259 }), 

260 write_file, 

261 ) 

262 executor.register( 

263 Tool.from_function("read_file", "读取文件内容", { 

264 "path": ToolParameter(type="string", description="文件路径"), 

265 }), 

266 read_file, 

267 ) 

268 executor.register( 

269 Tool.from_function("list_dir", "列出目录内容", { 

270 "path": ToolParameter(type="string", description="目录路径"), 

271 }), 

272 list_dir, 

273 ) 

274 

275 if os.getenv("OPENAI_API_KEY"): 

276 provider = create_provider("openai") 

277 elif os.getenv("DEEPSEEK_API_KEY"): 

278 provider = create_provider("deepseek") 

279 else: 

280 print("\n 文件操作演示需要 API Key(Mock 不支持动态文件操作)。") 

281 print(" 运行 agentos init 配置后重试。\n") 

282 return 

283 

284 agent = ToolAgent( 

285 provider=provider, 

286 tool_executor=executor, 

287 config=AgentConfig(verbose=verbose, temperature=0.0), 

288 system_prompt="你是文件操作助手。用工具完成任务后用中文汇报结果。", 

289 ) 

290 

291 demo_dir = "/tmp/agentos_file_demo" 

292 print(f"\n 演示目录: {demo_dir}") 

293 print() 

294 

295 result = agent.run(f"在 {demo_dir} 下创建 hello.txt 写入 'Hello from AgentOS!',列出目录内容,再读取 hello.txt 确认。") 

296 print(f"\n 耗时: {result.total_duration_ms/1000:.1f}s | 步数: {result.total_steps}") 

297 print(f" Token: {result.total_tokens} | 费用: ${result.total_cost_usd:.4f}") 

298 print(f" 结果: {result.final_answer}") 

299 

300 

301def _run_production_agent(cli_args: list[str], verbose: bool = False) -> None: 

302 """Run a task through ProductionAgent (ModelRouter + AuditLogger + SmartCache).""" 

303 host = "0.0.0.0" 

304 port = 8090 

305 

306 i = 0 

307 while i < len(cli_args): 

308 a = cli_args[i] 

309 if a == "--host" and i + 1 < len(cli_args): 

310 host = cli_args[i + 1]; i += 2; continue 

311 elif a == "--port" and i + 1 < len(cli_args): 

312 port = int(cli_args[i + 1]); i += 2; continue 

313 elif a == "--budget" and i + 1 < len(cli_args): 

314 i += 2; continue 

315 else: 

316 i += 1 

317 

318 try: 

319 from agentos.llm.factory import create_provider 

320 from agentos.agent.model_router import ModelRouter 

321 from agentos.llm.smart_cache import SmartCache, CacheConfig 

322 from agentos.agent.production import ProductionConfig 

323 from agentos.server.agent_api import create_agent_api 

324 except ImportError as e: 

325 print(f"Error: missing dependency — {e}") 

326 sys.exit(1) 

327 

328 try: 

329 provider = create_provider("openai") 

330 except Exception: 

331 print("Warning: no live provider, using mock.") 

332 from agentos.agent.tool_agent import MockLLMProvider 

333 provider = MockLLMProvider() 

334 

335 executor = _build_executor() 

336 router = ModelRouter.with_defaults(daily_budget_usd=50.0) 

337 cache = SmartCache(CacheConfig(enabled=True, fuzzy_enabled=True)) 

338 

339 app = create_agent_api( 

340 provider=provider, 

341 executor=executor, 

342 router=router, 

343 cache=cache, 

344 config=ProductionConfig(budget_usd=50.0), 

345 ) 

346 

347 import uvicorn 

348 print(f"AgentOS Production API → http://{host}:{port}") 

349 print(f" POST /agent/run GET /agent/health GET /agent/stats") 

350 uvicorn.run(app, host=host, port=port, log_level="info" if verbose else "warning") 

351 

352 

353def main(): 

354 args = sys.argv[1:] 

355 verbose = "--verbose" in args or "-v" in args 

356 args = [a for a in args if a not in ("--verbose", "-v")] 

357 

358 if not args or args[0] in ("help", "--help", "-h"): 

359 from agentos import __version__ 

360 from agentos.cli.init import config_status_text 

361 print(f"AgentOS v{__version__} — Production Agent Framework CLI\n") 

362 print(f"Provider: {config_status_text()}\n") 

363 print("Usage:") 

364 print(" agentos init Interactive setup wizard (recommended)") 

365 print(" agentos hello One-step health check & quick intro") 

366 print(" agentos config-panel Open web config panel in browser") 

367 print(" agentos <task> Run a task with the autonomous agent") 

368 print(" agentos run <task> Same as above") 

369 print(" agentos demo Run interactive demo (weather/stock/files)") 

370 print(" agentos serve Start API server (port 8080)") 

371 print(" agentos run [--port PORT] Start ProductionAgent API (port 8090)") 

372 print(" agentos daemon <cmd> Server daemon: start|stop|status|restart|run") 

373 print(" agentos version Show version") 

374 print(" agentos skills List agent marketplace skills") 

375 print(" agentos docs [output-dir] Generate API reference docs (→ docs/api/)") 

376 print(" agentos dashboard Open web trace dashboard (port 18500)") 

377 print(" agentos mcp-server Start MCP server (stdio) for Claude Desktop etc.") 

378 print(" agentos desktop Launch web desktop client (port 19999)") 

379 print(" agentos desktop-shell Launch native desktop shell (pywebview)") 

380 print(" agentos enterprise <cmd> Enterprise features: api-key, tenant, audit") 

381 print(" agentos marketplace <cmd> Skill marketplace: search|install|list|update|uninstall|stats") 

382 print(" agentos rollback <version> Rollback to a previous version (--list|--verify|--prune)") 

383 print("\nOptions:") 

384 print(" -v, --verbose Show agent step details") 

385 print(" --provider <name> Force provider: openai|deepseek|anthropic") 

386 print("\nProvider (auto-detect via env vars):") 

387 print(" OPENAI_API_KEY → OpenAI (gpt-4o-mini)") 

388 print(" DEEPSEEK_API_KEY → DeepSeek (deepseek-chat)") 

389 print(" ANTHROPIC_API_KEY → Anthropic (claude-sonnet-4)") 

390 print(" (none set) → Mock demo mode — run 'agentos init' to configure") 

391 print("\nExamples:") 

392 print(" agentos hello # 30s quick tour") 

393 print(" agentos init # 1-minute setup wizard") 

394 print(" agentos \"列出当前目录的文件\"") 

395 print(" agentos \"创建一个 hello.py 打印 Hello World\"") 

396 print(" agentos demo") 

397 sys.exit(0) 

398 

399 cmd = args[0] 

400 if cmd == "init": 

401 from agentos.cli.init import init_cli 

402 init_cli(args) 

403 return 

404 

405 if cmd == "config-panel": 

406 from agentos.cli.config_panel import start_panel 

407 start_panel() 

408 return 

409 

410 if cmd == "status": 

411 from agentos.cli.init import _detect_current_config, config_status_text 

412 print(f"Provider: {config_status_text()}\n") 

413 config = _detect_current_config() 

414 for name, info in config["providers"].items(): 

415 from agentos.cli.init import PROVIDERS 

416 p = PROVIDERS[name] 

417 status_icon = "✅" if info["env_set"] else "⬜" 

418 key_info = info.get("key_preview", "未配置") or "未配置" 

419 in_config = " (配置文件)" if info.get("in_config") else "" 

420 print(f" {status_icon} {p['label']:20s} {key_info:25s}{in_config}") 

421 return 

422 

423 if cmd == "version": 

424 from agentos import __version__ 

425 print(f"AgentOS v{__version__}") 

426 return 

427 

428 if cmd == "skills": 

429 from agentos.agents.market import AgentMarket 

430 market = AgentMarket() 

431 stats = market.stats() 

432 print(f"Agent Skill Market: {stats['total']} skills\n") 

433 for cat, count in stats["by_category"].items(): 

434 skills = market.list_by_category(cat) 

435 print(f" [{cat}] ({count})") 

436 for s in skills: 

437 print(f" {s.name}: {s.description}") 

438 return 

439 

440 if cmd == "docs": 

441 from agentos.docs.generator import generate_api_docs, generate_quickstart 

442 import os 

443 if not os.path.isdir("agentos"): 

444 print("Error: run 'agentos docs' from the agentos project root directory") 

445 sys.exit(1) 

446 output_dir = args[1] if len(args) > 1 else "docs" 

447 os.makedirs(output_dir, exist_ok=True) 

448 api_path = os.path.join(output_dir, "api_reference.md") 

449 qs_path = os.path.join(output_dir, "quickstart.md") 

450 md = generate_api_docs("agentos", api_path) 

451 generate_quickstart(qs_path) 

452 module_count = len([d for d in os.listdir("agentos") if os.path.isdir(os.path.join("agentos", d))]) 

453 print(f"Generated API docs: {api_path} ({len(md.splitlines())} lines)") 

454 print(f"Generated Quickstart: {qs_path}") 

455 print(f"Scanned ~{module_count} source modules") 

456 return 

457 

458 if cmd == "dashboard": 

459 from agentos.dashboard.server import start_dashboard 

460 print("Starting AgentOS Dashboard...") 

461 start_dashboard() 

462 return 

463 

464 if cmd == "mcp-server": 

465 from agentos.mcp.server import start_mcp_server 

466 port = 0 

467 for i, a in enumerate(args[1:]): 

468 if a == "--port" and i + 2 < len(args): 

469 port = int(args[i + 2]) 

470 start_mcp_server(port=port) 

471 return 

472 

473 if cmd == "desktop": 

474 from agentos.desktop.server import launch_desktop 

475 host = "0.0.0.0" 

476 port = 19999 

477 mode = "dev" 

478 for i, a in enumerate(args[1:]): 

479 if a == "--host" and i + 2 < len(args): 

480 host = args[i + 2] 

481 elif a == "--port" and i + 2 < len(args): 

482 port = int(args[i + 2]) 

483 elif a == "--safe": 

484 mode = "safe" 

485 launch_desktop(host=host, port=port, mode=mode) 

486 return 

487 

488 if cmd == "desktop-shell": 

489 from agentos.desktop.shell import main as shell_main 

490 sys.argv = [sys.argv[0]] + args[1:] 

491 shell_main() 

492 return 

493 

494 if cmd == "enterprise": 

495 _run_enterprise(args[1:]) 

496 return 

497 

498 if cmd == "marketplace": 

499 _run_marketplace(args[1:]) 

500 return 

501 

502 if cmd == "rollback": 

503 from agentos.cli.rollback import rollback_cli 

504 sys.exit(rollback_cli(args[1:])) 

505 

506 if cmd == "run": 

507 _run_production_agent(args[1:], verbose) 

508 return 

509 

510 if cmd == "serve": 

511 host = "0.0.0.0" 

512 port = 8080 

513 for i, a in enumerate(args[1:]): 

514 if a == "--host" and i + 2 < len(args): 

515 host = args[i + 2] 

516 elif a == "--port" and i + 2 < len(args): 

517 port = int(args[i + 2]) 

518 from agentos.api.server import AgentAPI 

519 from agentos.core.loop import AgentLoop, LoopConfig 

520 from agentos.core.context import ContextManager 

521 from agentos.tools.registry import ToolRegistry 

522 from agentos.models.router import ModelRouter, RECOMMENDED_CONFIG 

523 from agentos.tools.code_agent import CodeAgentTool, ShellTool 

524 from agentos.tools.file_tools import ReadFileTool, WriteFileTool, ListDirectoryTool 

525 from agentos.tools.web_tools import WebFetchTool 

526 registry = ToolRegistry() 

527 registry.register_many([ReadFileTool(), WriteFileTool(), ListDirectoryTool(), CodeAgentTool(), ShellTool(), WebFetchTool()]) 

528 ctx = ContextManager(system_prompt="AgentOS API Server v1.0") 

529 router = ModelRouter(RECOMMENDED_CONFIG) 

530 loop = AgentLoop(model_router=router, tool_registry=registry, context_manager=ctx) 

531 api = AgentAPI(loop) 

532 print(f"AgentOS API starting on http://{host}:{port}") 

533 api.serve(host=host, port=port) 

534 return 

535 

536 if cmd == "daemon": 

537 from agentos.server.daemon import daemon_main 

538 sys.exit(daemon_main(args[1:])) 

539 

540 if cmd == "hello": 

541 _run_hello() 

542 return 

543 

544 if cmd == "demo": 

545 print("=" * 60) 

546 print(" AgentOS — Interactive Demo") 

547 print("=" * 60) 

548 print() 

549 print(" 选择演示场景:") 

550 print(" [1] 天气助手(默认)") 

551 print(" [2] 文件操作") 

552 print(" [3] 健康自检") 

553 print() 

554 

555 try: 

556 choice = input(" 请输入数字 (1-3) [1]: ").strip() or "1" 

557 except (EOFError, KeyboardInterrupt): 

558 choice = "1" 

559 

560 if choice == "3": 

561 _run_hello() 

562 elif choice == "2": 

563 _run_file_demo(verbose) 

564 else: 

565 _run_demo(verbose) 

566 return 

567 

568 # Run task 

569 task_start = 1 if cmd == "run" else 0 

570 task = " ".join(args[task_start:]) 

571 if not task.strip(): 

572 no_task_provided() 

573 

574 agent = _build_agent(verbose=verbose) 

575 # 自动记录 tracker 

576 from agentos.dashboard.tracker import Tracker 

577 import uuid, time 

578 tracker = Tracker.get() 

579 session_id = f"run-{uuid.uuid4().hex[:12]}" 

580 rec = tracker.start_session(session_id, task, model="auto", provider="auto") 

581 t0 = time.time() 

582 try: 

583 result = agent.run(task) 

584 elapsed = (time.time() - t0) * 1000 

585 tracker.finish_session( 

586 session_id, 

587 status="completed" if result.success else "error", 

588 error=result.error if not result.success else "", 

589 total_cost=result.total_cost_usd if hasattr(result, 'total_cost_usd') else 0.0, 

590 ) 

591 print(f"\n{'─' * 60}") 

592 if result.success: 

593 print(f"Result ({(result.total_duration_ms/1000):.1f}s, " 

594 f"{result.total_steps} steps, " 

595 f"{result.total_tokens} tokens, " 

596 f"${result.total_cost_usd:.4f}):") 

597 print(f"{result.final_answer}") 

598 else: 

599 print(f"Error: {result.error}") 

600 except Exception as e: 

601 tracker.finish_session(session_id, status="error", error=str(e)) 

602 raise 

603 

604 

605def _run_marketplace(args: list[str]): 

606 """Skill Marketplace CLI dispatcher.""" 

607 

608 if not args or args[0] in ("help", "--help", "-h"): 

609 print("AgentOS Skill Marketplace\n") 

610 print("Subcommands:") 

611 print(" search <query> 搜索技能市场") 

612 print(" install <name|path|url> 安装技能(PyPI / 本地 / GitHub)") 

613 print(" list 列出已安装技能") 

614 print(" info <name> 查看技能详情") 

615 print(" update <name> 更新技能到最新版") 

616 print(" uninstall <name> 卸载技能") 

617 print(" stats 市场统计") 

618 print("\n兼容格式: agentos / openclaw / mcp / generic") 

619 return 

620 

621 from agentos.marketplace import SkillRegistry, InstallResult 

622 

623 registry = SkillRegistry() 

624 sub = args[0] 

625 rest = args[1:] 

626 

627 if sub == "search": 

628 query = " ".join(rest) if rest else "" 

629 print(f"Searching marketplace for '{query or 'all'}'...\n") 

630 results = registry.search(query) 

631 if not results: 

632 print("No skills found. Try a broader query, or publish your own with 'agentos-skill-<name>' on PyPI.") 

633 return 

634 print(f"{'Name':<24s} {'Version':<12s} {'Source':<10s} Description") 

635 print("-" * 80) 

636 for r in results: 

637 desc = r.description[:60] if r.description else "-" 

638 print(f"{r.name:<24s} {r.version:<12s} {r.source:<10s} {desc}") 

639 

640 elif sub == "install": 

641 if not rest: 

642 print("Usage: agentos marketplace install <name|path|url>") 

643 return 

644 target = " ".join(rest) 

645 print(f"Installing '{target}'...") 

646 result = registry.install(target) 

647 if result.success and result.manifest: 

648 m = result.manifest 

649 print(f"\n Installed: {m.name} v{m.version} [{m.format.value}]") 

650 print(f" Source: {result.install_type}") 

651 if m.description: 

652 print(f" Description: {m.description}") 

653 if result.dep_installed: 

654 print(f" Dependencies: {', '.join(result.dep_installed)}") 

655 if m.tools: 

656 print(f" Tools: {', '.join(t.name for t in m.tools)}") 

657 else: 

658 print(f" Failed: {result.error}") 

659 

660 elif sub == "list": 

661 skills = registry.list_installed() 

662 if not skills: 

663 print("No skills installed. Try 'agentos marketplace install <name>'.") 

664 return 

665 print(f"{'Name':<24s} {'Version':<12s} {'Format':<12s} {'Source':<10s} Description") 

666 print("-" * 90) 

667 for m in skills: 

668 desc = (m.description or "")[:50] 

669 print(f"{m.name:<24s} {m.version:<12s} {m.format.value:<12s} {m.source:<10s} {desc}") 

670 

671 elif sub == "info": 

672 if not rest: 

673 print("Usage: agentos marketplace info <name>") 

674 return 

675 name = rest[0] 

676 m = registry.get_installed(name) 

677 if not m: 

678 print(f"Skill '{name}' not installed. Try 'agentos marketplace search {name}'.") 

679 return 

680 print(f"\n {m.name} v{m.version} [{m.format.value}]") 

681 print(f" {'─' * 40}") 

682 print(f" 描述: {m.description or '-'}") 

683 print(f" 作者: {m.author}") 

684 print(f" 许可: {m.license_}") 

685 print(f" 格式: {m.format.value}") 

686 print(f" 来源: {m.source}") 

687 print(f" 安装路径: {m.install_path or '-'}") 

688 if m.entrypoint: 

689 print(f" 入口: {m.entrypoint}") 

690 if m.repository: 

691 print(f" 仓库: {m.repository}") 

692 if m.homepage: 

693 print(f" 主页: {m.homepage}") 

694 if m.tags: 

695 print(f" 标签: {', '.join(m.tags)}") 

696 if m.tools: 

697 print(f" 工具 ({len(m.tools)}):") 

698 for t in m.tools: 

699 print(f" - {t.name}: {t.description or 'N/A'}") 

700 if m.dependencies: 

701 print(f" 依赖: {', '.join(m.dependencies)}") 

702 if m.mcp_command: 

703 print(f" MCP: {m.mcp_command} {' '.join(m.mcp_args)}") 

704 print(f" MCP 类型: {m.mcp_type}") 

705 if m.min_agentos_version: 

706 print(f" 要求版本: >= {m.min_agentos_version}") 

707 print() 

708 

709 elif sub == "update": 

710 if not rest: 

711 print("Usage: agentos marketplace update <name>") 

712 return 

713 name = rest[0] 

714 print(f"Updating '{name}'...") 

715 result = registry.update(name) 

716 if result.success and result.manifest: 

717 print(f" Updated: {result.manifest.name} v{result.manifest.version}") 

718 else: 

719 print(f" Failed: {result.error}") 

720 

721 elif sub == "uninstall": 

722 if not rest: 

723 print("Usage: agentos marketplace uninstall <name>") 

724 return 

725 name = rest[0] 

726 if registry.uninstall(name): 

727 print(f"Uninstalled: {name}") 

728 else: 

729 print(f"Skill '{name}' not installed.") 

730 

731 elif sub == "stats": 

732 stats = registry.stats() 

733 print(f"Marketplace Stats:") 

734 print(f" Total installed: {stats['total']}") 

735 print(f" Market dir: {stats['market_dir']}") 

736 if stats.get("by_format"): 

737 print(f" By format:") 

738 for fmt, count in stats["by_format"].items(): 

739 print(f" {fmt}: {count}") 

740 

741 else: 

742 print(f"Unknown marketplace command: {sub}") 

743 print("Try: agentos marketplace --help") 

744 

745 

746def _run_enterprise(args: list[str]): 

747 """Enterprise CLI dispatcher.""" 

748 

749 if not args or args[0] in ("help", "--help", "-h"): 

750 print("AgentOS Enterprise CLI\n") 

751 print("Subcommands:") 

752 print(" api-key create|list|revoke|stats API Key management") 

753 print(" tenant create|list|stats Multi-tenant management") 

754 print(" audit stats|recent|export Audit logging") 

755 return 

756 

757 sub = args[0] 

758 from agentos.enterprise import ( 

759 APIKeyManager, TenantManager, AuditLogger, 

760 KeyCreateRequest, KeyScope, TenantTier, 

761 ) 

762 

763 if sub == "api-key": 

764 _run_enterprise_api_key(args[1:], APIKeyManager()) 

765 elif sub == "tenant": 

766 _run_enterprise_tenant(args[1:], TenantManager()) 

767 elif sub == "audit": 

768 _run_enterprise_audit(args[1:], AuditLogger()) 

769 else: 

770 print(f"Unknown enterprise subcommand: {sub}") 

771 print("Try: agentos enterprise --help") 

772 

773 

774def _run_enterprise_api_key(args: list[str], mgr): 

775 if not args: 

776 print("Usage: agentos enterprise api-key <create|list|revoke|stats>") 

777 return 

778 cmd = args[0] 

779 if cmd == "create": 

780 name = args[1] if len(args) > 1 else "cli-key" 

781 result = mgr.create_key(KeyCreateRequest(name=name, scopes=[KeyScope.AGENT_RUN, KeyScope.READ])) 

782 print(f"Key created: {result.key_id}") 

783 print(f"Plaintext (only shown once): {result.plaintext_key}") 

784 print(f"Prefix: {result.key_prefix}") 

785 print(f"Scopes: {[s.value for s in result.scopes]}") 

786 elif cmd == "list": 

787 keys = mgr.list_keys() 

788 if not keys: 

789 print("No API keys.") 

790 return 

791 print(f"{'ID':<28s} {'Name':<24s} {'Status':<10s} {'Usage':<8s}") 

792 print("-" * 72) 

793 for k in keys: 

794 status = "revoked" if k.revoked else "active" 

795 print(f"{k.key_id:<28s} {k.name:<24s} {status:<10s} {k.usage_count:<8d}") 

796 elif cmd == "revoke": 

797 if len(args) < 2: 

798 print("Usage: agentos enterprise api-key revoke <key_id>") 

799 return 

800 ok = mgr.revoke_key(args[1]) 

801 print(f"{'Revoked' if ok else 'Not found or already revoked'}: {args[1]}") 

802 elif cmd == "stats": 

803 stats = mgr.stats() 

804 print(f"Total: {stats['total']} Active: {stats['active']} Revoked: {stats['revoked']} " 

805 f"Total usage: {stats['total_usage_count']}") 

806 else: 

807 print(f"Unknown api-key command: {cmd}") 

808 

809 

810def _run_enterprise_tenant(args: list[str], mgr): 

811 if not args: 

812 print("Usage: agentos enterprise tenant <create|list|stats>") 

813 return 

814 cmd = args[0] 

815 if cmd == "create": 

816 name = args[1] if len(args) > 1 else "default" 

817 tier_str = args[2] if len(args) > 2 else "free" 

818 tier = TenantTier(tier_str) if tier_str in [t.value for t in TenantTier] else TenantTier.FREE 

819 tenant = mgr.create_tenant(name=name, tier=tier) 

820 print(f"Tenant created: {tenant.tenant_id}") 

821 print(f"Name: {tenant.name} Tier: {tenant.tier.value}") 

822 elif cmd == "list": 

823 tenants = mgr.list_tenants() 

824 if not tenants: 

825 print("No tenants.") 

826 return 

827 print(f"{'ID':<20s} {'Name':<20s} {'Tier':<12s} {'Status':<10s}") 

828 print("-" * 64) 

829 for t in tenants: 

830 print(f"{t.tenant_id:<20s} {t.name:<20s} {t.tier.value:<12s} {t.status.value:<10s}") 

831 elif cmd == "stats": 

832 stats = mgr.stats() 

833 print(f"Total tenants: {stats['total']}") 

834 print(f"By tier: {stats['by_tier']}") 

835 print(f"By status: {stats['by_status']}") 

836 else: 

837 print(f"Unknown tenant command: {cmd}") 

838 

839 

840def _run_enterprise_audit(args: list[str], logger): 

841 if not args: 

842 print("Usage: agentos enterprise audit <stats|recent|export>") 

843 return 

844 cmd = args[0] 

845 if cmd == "stats": 

846 stats = logger.stats() 

847 print(f"Total events: {stats['total_events']}") 

848 print(f"By category: {stats['by_category']}") 

849 print(f"By severity: {stats['by_severity']}") 

850 elif cmd == "recent": 

851 n = int(args[1]) if len(args) > 1 else 10 

852 events = logger.get_recent(n) 

853 if not events: 

854 print("No audit events.") 

855 return 

856 for e in events: 

857 ts = __import__('time').strftime("%H:%M:%S", __import__('time').gmtime(e.timestamp)) 

858 print(f"[{ts}] {e.category.value:8s} {e.action:24s} {e.status}") 

859 elif cmd == "export": 

860 fmt = args[1] if len(args) > 1 else "json" 

861 if fmt == "csv": 

862 print(logger.export_csv()[:2000]) 

863 else: 

864 print(logger.export_json()[:2000]) 

865 print("... (truncated to 2000 chars)") 

866 else: 

867 print(f"Unknown audit command: {cmd}") 

868 

869 

870def _run_demo(verbose: bool): 

871 from agentos.llm import create_provider, Tool, ToolParameter 

872 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig 

873 

874 def get_weather(city: str) -> str: 

875 data = { 

876 "北京": "北京:晴,22°C,湿度 35%,东北风 3 级", 

877 "上海": "上海:多云转阴,28°C,湿度 70%,东南风 2 级", 

878 "深圳": "深圳:雷阵雨,31°C,湿度 85%", 

879 } 

880 return data.get(city, f"{city}: 数据暂缺") 

881 

882 def get_stock(symbol: str) -> str: 

883 prices = { 

884 "AAPL": "$220.50 (+1.2%)", 

885 "TSLA": "$248.30 (-0.8%)", 

886 } 

887 return prices.get(symbol.upper(), f"{symbol}: 未找到") 

888 

889 executor = ToolExecutor() 

890 executor.register( 

891 Tool.from_function("get_weather", "获取城市天气", { 

892 "city": ToolParameter(type="string", description="城市名"), 

893 }), 

894 get_weather, 

895 ) 

896 executor.register( 

897 Tool.from_function("get_stock_price", "获取股票价格", { 

898 "symbol": ToolParameter(type="string", description="股票代码,如 AAPL"), 

899 }), 

900 get_stock, 

901 ) 

902 

903 if os.getenv("OPENAI_API_KEY"): 

904 provider = create_provider("openai") 

905 elif os.getenv("DEEPSEEK_API_KEY"): 

906 provider = create_provider("deepseek") 

907 else: 

908 print("\n ⚠️ Mock 演示 — 配置 API Key 获取真实 AI 响应\n") 

909 print(" 运行: agentos init\n") 

910 provider = MockLLMProvider([ 

911 MockLLMProvider.tool_response( 

912 "get_weather", {"city": "北京"}, tool_call_id="tc_w1", 

913 ), 

914 MockLLMProvider.text_response( 

915 "北京目前天气晴,气温 22°C,湿度 35%,东北风 3 级。" 

916 "适合户外活动,建议带薄外套。" 

917 ), 

918 ]) 

919 

920 agent = ToolAgent( 

921 provider=provider, 

922 tool_executor=executor, 

923 config=AgentConfig(verbose=verbose, temperature=0.0), 

924 system_prompt="你是一个天气助手。用工具获取天气/股票信息后直接回答。", 

925 ) 

926 

927 result = agent.run("北京天气怎么样?") 

928 print(f"\nTask: 北京天气怎么样?") 

929 print(f"Steps: {result.total_steps} | Time: {(result.total_duration_ms/1000):.1f}s") 

930 print(f"Tokens: {result.total_tokens} | Cost: ${result.total_cost_usd:.4f}") 

931 print(f"Answer: {result.final_answer}") 

932 

933 

934if __name__ == "__main__": 

935 main()