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

651 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 12:29 +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 os 

8import sys 

9 

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

11from agentos.cli.errors import ( 

12 no_provider_configured, 

13 no_task_provided, 

14 welcome, 

15) 

16from agentos.llm.base import Tool, ToolParameter 

17from agentos.llm.factory import create_provider 

18 

19 

20def _build_executor() -> ToolExecutor: 

21 executor = ToolExecutor() 

22 

23 # Shell tool 

24 executor.register( 

25 Tool.from_function( 

26 name="run_shell", 

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

28 parameters={ 

29 "command": ToolParameter( 

30 type="string", 

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

32 ), 

33 }, 

34 ), 

35 lambda command: _run_shell_unsafe(command), 

36 ) 

37 

38 # File tools 

39 executor.register( 

40 Tool.from_function( 

41 name="read_file", 

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

43 parameters={ 

44 "file_path": ToolParameter( 

45 type="string", 

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

47 ), 

48 }, 

49 ), 

50 lambda file_path: _read_file(file_path), 

51 ) 

52 

53 executor.register( 

54 Tool.from_function( 

55 name="list_directory", 

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

57 parameters={ 

58 "path": ToolParameter( 

59 type="string", 

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

61 ), 

62 }, 

63 ), 

64 lambda path: _list_directory(path), 

65 ) 

66 

67 executor.register( 

68 Tool.from_function( 

69 name="write_file", 

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

71 parameters={ 

72 "file_path": ToolParameter( 

73 type="string", 

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

75 ), 

76 "content": ToolParameter( 

77 type="string", 

78 description="Text content to write.", 

79 ), 

80 }, 

81 ), 

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

83 ) 

84 

85 return executor 

86 

87 

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

89 import subprocess 

90 import tempfile 

91 

92 try: 

93 with tempfile.TemporaryDirectory() as td: 

94 result = subprocess.run( 

95 command, 

96 shell=True, 

97 capture_output=True, 

98 text=True, 

99 timeout=30, 

100 cwd=td, 

101 ) 

102 out = result.stdout.strip() 

103 err = result.stderr.strip() 

104 if err: 

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

106 return out or "(no output)" 

107 except subprocess.TimeoutExpired: 

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

109 except Exception as e: 

110 return f"Error: {e}" 

111 

112 

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

114 try: 

115 with open(file_path) as f: 

116 return f.read() 

117 except Exception as e: 

118 return f"Error: {e}" 

119 

120 

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

122 try: 

123 entries = os.listdir(path) 

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

125 except Exception as e: 

126 return f"Error: {e}" 

127 

128 

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

130 try: 

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

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

133 f.write(content) 

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

135 except Exception as e: 

136 return f"Error: {e}" 

137 

138 

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

140 if provider_name: 

141 provider = create_provider(provider_name) 

142 elif os.getenv("OPENAI_API_KEY"): 

143 provider = create_provider("openai") 

144 provider_name = "openai" 

145 elif os.getenv("DEEPSEEK_API_KEY"): 

146 provider = create_provider("deepseek") 

147 provider_name = "deepseek" 

148 elif os.getenv("ANTHROPIC_API_KEY"): 

149 provider = create_provider("anthropic") 

150 provider_name = "anthropic" 

151 else: 

152 welcome() 

153 no_provider_configured() 

154 

155 config = AgentConfig(verbose=verbose) 

156 executor = _build_executor() 

157 

158 return ToolAgent( 

159 provider=provider, 

160 tool_executor=executor, 

161 config=config, 

162 system_prompt=( 

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

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

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

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

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

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

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

170 "使用中文回复。" 

171 ), 

172 ) 

173 

174 

175def _run_hello(): 

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

177 import time 

178 

179 from agentos import __version__ 

180 

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

182 print(" \033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m") 

183 print() 

184 

185 # Step 1: check provider 

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

187 provider_color = "\033[33m" 

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

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

190 provider_color = "\033[32m" 

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

192 provider_label = "DeepSeek (deepseek-chat)" 

193 provider_color = "\033[32m" 

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

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

196 provider_color = "\033[32m" 

197 

198 steps = [ 

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

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

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

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

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

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

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

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

207 ] 

208 

209 for label, status in steps: 

210 time.sleep(0.12) 

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

212 

213 time.sleep(0.3) 

214 print() 

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

216 print() 

217 

218 if ( 

219 os.environ.get("OPENAI_API_KEY") 

220 or os.environ.get("DEEPSEEK_API_KEY") 

221 or os.environ.get("ANTHROPIC_API_KEY") 

222 ): 

223 print(" 快速开始:") 

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

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

226 else: 

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

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

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

230 print() 

231 

232 

233def _run_file_demo(verbose: bool): 

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

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

236 

237 executor = ToolExecutor() 

238 

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

240 import os as _os 

241 

242 try: 

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

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

245 f.write(content) 

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

247 except Exception as e: 

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

249 

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

251 try: 

252 with open(path) as f: 

253 return f.read() 

254 except Exception as e: 

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

256 

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

258 import os as _os 

259 

260 try: 

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

262 except Exception as e: 

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

264 

265 executor.register( 

266 Tool.from_function( 

267 "write_file", 

268 "写入文本到文件", 

269 { 

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

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

272 }, 

273 ), 

274 write_file, 

275 ) 

276 executor.register( 

277 Tool.from_function( 

278 "read_file", 

279 "读取文件内容", 

280 { 

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

282 }, 

283 ), 

284 read_file, 

285 ) 

286 executor.register( 

287 Tool.from_function( 

288 "list_dir", 

289 "列出目录内容", 

290 { 

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

292 }, 

293 ), 

294 list_dir, 

295 ) 

296 

297 if os.getenv("OPENAI_API_KEY"): 

298 provider = create_provider("openai") 

299 elif os.getenv("DEEPSEEK_API_KEY"): 

300 provider = create_provider("deepseek") 

301 else: 

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

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

304 return 

305 

306 agent = ToolAgent( 

307 provider=provider, 

308 tool_executor=executor, 

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

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

311 ) 

312 

313 demo_dir = "/tmp/agentos_file_demo" 

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

315 print() 

316 

317 result = agent.run( 

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

319 ) 

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

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

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

323 

324 

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

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

327 host = "0.0.0.0" 

328 port = 8090 

329 

330 i = 0 

331 while i < len(cli_args): 

332 a = cli_args[i] 

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

334 host = cli_args[i + 1] 

335 i += 2 

336 continue 

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

338 port = int(cli_args[i + 1]) 

339 i += 2 

340 continue 

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

342 i += 2 

343 continue 

344 else: 

345 i += 1 

346 

347 try: 

348 from agentos.agent.model_router import ModelRouter 

349 from agentos.agent.production import ProductionConfig 

350 from agentos.llm.factory import create_provider 

351 from agentos.llm.smart_cache import CacheConfig, SmartCache 

352 from agentos.server.agent_api import create_agent_api 

353 except ImportError as e: 

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

355 sys.exit(1) 

356 

357 try: 

358 provider = create_provider("openai") 

359 except Exception: 

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

361 from agentos.agent.tool_agent import MockLLMProvider 

362 

363 provider = MockLLMProvider() 

364 

365 executor = _build_executor() 

366 router = ModelRouter.with_defaults(daily_budget_usd=50.0) 

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

368 

369 app = create_agent_api( 

370 provider=provider, 

371 executor=executor, 

372 router=router, 

373 cache=cache, 

374 config=ProductionConfig(budget_usd=50.0), 

375 ) 

376 

377 import uvicorn 

378 

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

380 print(" POST /agent/run GET /agent/health GET /agent/stats") 

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

382 

383 

384def main(): 

385 args = sys.argv[1:] 

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

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

388 

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

390 from agentos import __version__ 

391 from agentos.cli.init import config_status_text 

392 

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

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

395 print("Usage:") 

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

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

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

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

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

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

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

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

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

405 print(" agentos version Show version") 

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

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

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

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

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

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

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

413 print( 

414 " agentos marketplace <cmd> Skill marketplace: search|install|list|update|uninstall|stats" 

415 ) 

416 print( 

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

418 ) 

419 print("\nOptions:") 

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

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

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

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

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

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

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

427 print("\nExamples:") 

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

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

430 print(' agentos "列出当前目录的文件"') 

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

432 print(" agentos demo") 

433 sys.exit(0) 

434 

435 cmd = args[0] 

436 if cmd == "init": 

437 from agentos.cli.init import init_cli 

438 

439 init_cli(args) 

440 return 

441 

442 if cmd == "config-panel": 

443 from agentos.cli.config_panel import start_panel 

444 

445 start_panel() 

446 return 

447 

448 if cmd == "status": 

449 from agentos.cli.init import _detect_current_config, config_status_text 

450 

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

452 config = _detect_current_config() 

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

454 from agentos.cli.init import PROVIDERS 

455 

456 p = PROVIDERS[name] 

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

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

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

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

461 return 

462 

463 if cmd == "version": 

464 from agentos import __version__ 

465 

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

467 return 

468 

469 if cmd == "skills": 

470 from agentos.agents.market import AgentMarket 

471 

472 market = AgentMarket() 

473 stats = market.stats() 

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

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

476 skills = market.list_by_category(cat) 

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

478 for s in skills: 

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

480 return 

481 

482 if cmd == "docs": 

483 import os 

484 

485 from agentos.docs.generator import generate_api_docs, generate_quickstart 

486 

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

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

489 sys.exit(1) 

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

491 os.makedirs(output_dir, exist_ok=True) 

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

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

494 md = generate_api_docs("agentos", api_path) 

495 generate_quickstart(qs_path) 

496 module_count = len( 

497 [d for d in os.listdir("agentos") if os.path.isdir(os.path.join("agentos", d))] 

498 ) 

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

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

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

502 return 

503 

504 if cmd == "dashboard": 

505 from agentos.dashboard.server import start_dashboard 

506 

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

508 start_dashboard() 

509 return 

510 

511 if cmd == "mcp-server": 

512 from agentos.mcp.server import start_mcp_server 

513 

514 port = 0 

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

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

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

518 start_mcp_server(port=port) 

519 return 

520 

521 if cmd == "desktop": 

522 from agentos.desktop.server import launch_desktop 

523 

524 host = "0.0.0.0" 

525 port = 19999 

526 mode = "dev" 

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

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

529 host = args[i + 2] 

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

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

532 elif a == "--safe": 

533 mode = "safe" 

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

535 return 

536 

537 if cmd == "desktop-shell": 

538 from agentos.desktop.shell import main as shell_main 

539 

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

541 shell_main() 

542 return 

543 

544 if cmd == "enterprise": 

545 _run_enterprise(args[1:]) 

546 return 

547 

548 if cmd == "marketplace": 

549 _run_marketplace(args[1:]) 

550 return 

551 

552 if cmd == "rollback": 

553 from agentos.cli.rollback import rollback_cli 

554 

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

556 

557 if cmd == "run": 

558 _run_production_agent(args[1:], verbose) 

559 return 

560 

561 if cmd == "serve": 

562 host = "0.0.0.0" 

563 port = 8080 

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

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

566 host = args[i + 2] 

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

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

569 from agentos.api.server import AgentAPI 

570 from agentos.core.context import ContextManager 

571 from agentos.core.loop import AgentLoop 

572 from agentos.models.router import RECOMMENDED_CONFIG, ModelRouter 

573 from agentos.tools.code_agent import CodeAgentTool, ShellTool 

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

575 from agentos.tools.registry import ToolRegistry 

576 from agentos.tools.web_tools import WebFetchTool 

577 

578 registry = ToolRegistry() 

579 registry.register_many( 

580 [ 

581 ReadFileTool(), 

582 WriteFileTool(), 

583 ListDirectoryTool(), 

584 CodeAgentTool(), 

585 ShellTool(), 

586 WebFetchTool(), 

587 ] 

588 ) 

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

590 router = ModelRouter(RECOMMENDED_CONFIG) 

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

592 api = AgentAPI(loop) 

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

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

595 return 

596 

597 if cmd == "daemon": 

598 from agentos.server.daemon import daemon_main 

599 

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

601 

602 if cmd == "hello": 

603 _run_hello() 

604 return 

605 

606 if cmd == "demo": 

607 print("=" * 60) 

608 print(" AgentOS — Interactive Demo") 

609 print("=" * 60) 

610 print() 

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

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

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

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

615 print() 

616 

617 try: 

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

619 except (EOFError, KeyboardInterrupt): 

620 choice = "1" 

621 

622 if choice == "3": 

623 _run_hello() 

624 elif choice == "2": 

625 _run_file_demo(verbose) 

626 else: 

627 _run_demo(verbose) 

628 return 

629 

630 # Run task 

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

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

633 if not task.strip(): 

634 no_task_provided() 

635 

636 agent = _build_agent(verbose=verbose) 

637 # 自动记录 tracker 

638 import time 

639 import uuid 

640 

641 from agentos.dashboard.tracker import Tracker 

642 

643 tracker = Tracker.get() 

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

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

646 t0 = time.time() 

647 try: 

648 result = agent.run(task) 

649 (time.time() - t0) * 1000 

650 tracker.finish_session( 

651 session_id, 

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

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

654 total_cost=result.total_cost_usd if hasattr(result, "total_cost_usd") else 0.0, 

655 ) 

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

657 if result.success: 

658 print( 

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

660 f"{result.total_steps} steps, " 

661 f"{result.total_tokens} tokens, " 

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

663 ) 

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

665 else: 

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

667 except Exception as e: 

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

669 raise 

670 

671 

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

673 """Skill Marketplace CLI dispatcher.""" 

674 

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

676 print("AgentOS Skill Marketplace\n") 

677 print("Subcommands:") 

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

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

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

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

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

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

684 print(" stats 市场统计") 

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

686 return 

687 

688 from agentos.marketplace import SkillRegistry 

689 

690 registry = SkillRegistry() 

691 sub = args[0] 

692 rest = args[1:] 

693 

694 if sub == "search": 

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

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

697 results = registry.search(query) 

698 if not results: 

699 print( 

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

701 ) 

702 return 

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

704 print("-" * 80) 

705 for r in results: 

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

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

708 

709 elif sub == "install": 

710 if not rest: 

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

712 return 

713 target = " ".join(rest) 

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

715 result = registry.install(target) 

716 if result.success and result.manifest: 

717 m = result.manifest 

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

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

720 if m.description: 

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

722 if result.dep_installed: 

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

724 if m.tools: 

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

726 else: 

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

728 

729 elif sub == "list": 

730 skills = registry.list_installed() 

731 if not skills: 

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

733 return 

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

735 print("-" * 90) 

736 for m in skills: 

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

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

739 

740 elif sub == "info": 

741 if not rest: 

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

743 return 

744 name = rest[0] 

745 m = registry.get_installed(name) 

746 if not m: 

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

748 return 

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

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

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

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

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

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

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

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

757 if m.entrypoint: 

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

759 if m.repository: 

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

761 if m.homepage: 

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

763 if m.tags: 

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

765 if m.tools: 

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

767 for t in m.tools: 

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

769 if m.dependencies: 

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

771 if m.mcp_command: 

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

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

774 if m.min_agentos_version: 

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

776 print() 

777 

778 elif sub == "update": 

779 if not rest: 

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

781 return 

782 name = rest[0] 

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

784 result = registry.update(name) 

785 if result.success and result.manifest: 

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

787 else: 

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

789 

790 elif sub == "uninstall": 

791 if not rest: 

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

793 return 

794 name = rest[0] 

795 if registry.uninstall(name): 

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

797 else: 

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

799 

800 elif sub == "stats": 

801 stats = registry.stats() 

802 print("Marketplace Stats:") 

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

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

805 if stats.get("by_format"): 

806 print(" By format:") 

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

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

809 

810 else: 

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

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

813 

814 

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

816 """Enterprise CLI dispatcher.""" 

817 

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

819 print("AgentOS Enterprise CLI\n") 

820 print("Subcommands:") 

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

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

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

824 return 

825 

826 sub = args[0] 

827 from agentos.enterprise import ( 

828 APIKeyManager, 

829 AuditLogger, 

830 TenantManager, 

831 ) 

832 

833 if sub == "api-key": 

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

835 elif sub == "tenant": 

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

837 elif sub == "audit": 

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

839 else: 

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

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

842 

843 

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

845 if not args: 

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

847 return 

848 cmd = args[0] 

849 if cmd == "create": 

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

851 result = mgr.create_key( 

852 KeyCreateRequest(name=name, scopes=[KeyScope.AGENT_RUN, KeyScope.READ]) 

853 ) 

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

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

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

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

858 elif cmd == "list": 

859 keys = mgr.list_keys() 

860 if not keys: 

861 print("No API keys.") 

862 return 

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

864 print("-" * 72) 

865 for k in keys: 

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

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

868 elif cmd == "revoke": 

869 if len(args) < 2: 

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

871 return 

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

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

874 elif cmd == "stats": 

875 stats = mgr.stats() 

876 print( 

877 f"Total: {stats['total']} Active: {stats['active']} Revoked: {stats['revoked']} " 

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

879 ) 

880 else: 

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

882 

883 

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

885 if not args: 

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

887 return 

888 cmd = args[0] 

889 if cmd == "create": 

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

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

892 tier = ( 

893 TenantTier(tier_str) if tier_str in [t.value for t in TenantTier] else TenantTier.FREE 

894 ) 

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

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

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

898 elif cmd == "list": 

899 tenants = mgr.list_tenants() 

900 if not tenants: 

901 print("No tenants.") 

902 return 

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

904 print("-" * 64) 

905 for t in tenants: 

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

907 elif cmd == "stats": 

908 stats = mgr.stats() 

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

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

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

912 else: 

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

914 

915 

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

917 if not args: 

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

919 return 

920 cmd = args[0] 

921 if cmd == "stats": 

922 stats = logger.stats() 

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

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

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

926 elif cmd == "recent": 

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

928 events = logger.get_recent(n) 

929 if not events: 

930 print("No audit events.") 

931 return 

932 for e in events: 

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

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

935 elif cmd == "export": 

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

937 if fmt == "csv": 

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

939 else: 

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

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

942 else: 

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

944 

945 

946def _run_demo(verbose: bool): 

947 from agentos.agent import AgentConfig, ToolAgent, ToolExecutor 

948 from agentos.llm import Tool, ToolParameter, create_provider 

949 

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

951 data = { 

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

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

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

955 } 

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

957 

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

959 prices = { 

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

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

962 } 

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

964 

965 executor = ToolExecutor() 

966 executor.register( 

967 Tool.from_function( 

968 "get_weather", 

969 "获取城市天气", 

970 { 

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

972 }, 

973 ), 

974 get_weather, 

975 ) 

976 executor.register( 

977 Tool.from_function( 

978 "get_stock_price", 

979 "获取股票价格", 

980 { 

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

982 }, 

983 ), 

984 get_stock, 

985 ) 

986 

987 if os.getenv("OPENAI_API_KEY"): 

988 provider = create_provider("openai") 

989 elif os.getenv("DEEPSEEK_API_KEY"): 

990 provider = create_provider("deepseek") 

991 else: 

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

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

994 provider = MockLLMProvider( 

995 [ 

996 MockLLMProvider.tool_response( 

997 "get_weather", 

998 {"city": "北京"}, 

999 tool_call_id="tc_w1", 

1000 ), 

1001 MockLLMProvider.text_response( 

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

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

1004 ), 

1005 ] 

1006 ) 

1007 

1008 agent = ToolAgent( 

1009 provider=provider, 

1010 tool_executor=executor, 

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

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

1013 ) 

1014 

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

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

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

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

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

1020 

1021 

1022if __name__ == "__main__": 

1023 main()