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

645 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-06 10:59 +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.llm.factory import create_provider 

11from agentos.llm.base import Tool, ToolParameter 

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

13from agentos.cli.errors import ( 

14 no_provider_configured, 

15 no_task_provided, 

16 welcome, 

17) 

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 try: 

92 with tempfile.TemporaryDirectory() as td: 

93 result = subprocess.run( 

94 command, shell=True, capture_output=True, text=True, 

95 timeout=30, cwd=td, 

96 ) 

97 out = result.stdout.strip() 

98 err = result.stderr.strip() 

99 if err: 

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

101 return out or "(no output)" 

102 except subprocess.TimeoutExpired: 

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

104 except Exception as e: 

105 return f"Error: {e}" 

106 

107 

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

109 try: 

110 with open(file_path) as f: 

111 return f.read() 

112 except Exception as e: 

113 return f"Error: {e}" 

114 

115 

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

117 try: 

118 entries = os.listdir(path) 

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

120 except Exception as e: 

121 return f"Error: {e}" 

122 

123 

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

125 try: 

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

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

128 f.write(content) 

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

130 except Exception as e: 

131 return f"Error: {e}" 

132 

133 

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

135 if provider_name: 

136 provider = create_provider(provider_name) 

137 elif os.getenv("OPENAI_API_KEY"): 

138 provider = create_provider("openai") 

139 provider_name = "openai" 

140 elif os.getenv("DEEPSEEK_API_KEY"): 

141 provider = create_provider("deepseek") 

142 provider_name = "deepseek" 

143 elif os.getenv("ANTHROPIC_API_KEY"): 

144 provider = create_provider("anthropic") 

145 provider_name = "anthropic" 

146 else: 

147 welcome() 

148 no_provider_configured() 

149 

150 config = AgentConfig(verbose=verbose) 

151 executor = _build_executor() 

152 

153 return ToolAgent( 

154 provider=provider, 

155 tool_executor=executor, 

156 config=config, 

157 system_prompt=( 

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

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

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

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

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

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

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

165 "使用中文回复。" 

166 ), 

167 ) 

168 

169 

170def _run_hello(): 

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

172 from agentos import __version__ 

173 import time 

174 

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

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

177 print() 

178 

179 # Step 1: check provider 

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

181 provider_color = "\033[33m" 

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

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

184 provider_color = "\033[32m" 

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

186 provider_label = "DeepSeek (deepseek-chat)" 

187 provider_color = "\033[32m" 

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

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

190 provider_color = "\033[32m" 

191 

192 steps = [ 

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

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

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

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

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

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

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

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

201 ] 

202 

203 for label, status in steps: 

204 time.sleep(0.12) 

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

206 

207 time.sleep(0.3) 

208 print() 

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

210 print() 

211 

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

213 print(" 快速开始:") 

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

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

216 else: 

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

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

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

220 print() 

221 

222 

223def _run_file_demo(verbose: bool): 

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

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

226 

227 executor = ToolExecutor() 

228 

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

230 import os as _os 

231 try: 

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

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

234 f.write(content) 

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

236 except Exception as e: 

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

238 

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

240 try: 

241 with open(path) as f: 

242 return f.read() 

243 except Exception as e: 

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

245 

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

247 import os as _os 

248 try: 

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

250 except Exception as e: 

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

252 

253 executor.register( 

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

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

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

257 }), 

258 write_file, 

259 ) 

260 executor.register( 

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

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

263 }), 

264 read_file, 

265 ) 

266 executor.register( 

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

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

269 }), 

270 list_dir, 

271 ) 

272 

273 if os.getenv("OPENAI_API_KEY"): 

274 provider = create_provider("openai") 

275 elif os.getenv("DEEPSEEK_API_KEY"): 

276 provider = create_provider("deepseek") 

277 else: 

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

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

280 return 

281 

282 agent = ToolAgent( 

283 provider=provider, 

284 tool_executor=executor, 

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

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

287 ) 

288 

289 demo_dir = "/tmp/agentos_file_demo" 

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

291 print() 

292 

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

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

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

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

297 

298 

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

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

301 host = "0.0.0.0" 

302 port = 8090 

303 

304 i = 0 

305 while i < len(cli_args): 

306 a = cli_args[i] 

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

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

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

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

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

312 i += 2; continue 

313 else: 

314 i += 1 

315 

316 try: 

317 from agentos.llm.factory import create_provider 

318 from agentos.agent.model_router import ModelRouter 

319 from agentos.llm.smart_cache import SmartCache, CacheConfig 

320 from agentos.agent.production import ProductionConfig 

321 from agentos.server.agent_api import create_agent_api 

322 except ImportError as e: 

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

324 sys.exit(1) 

325 

326 try: 

327 provider = create_provider("openai") 

328 except Exception: 

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

330 from agentos.agent.tool_agent import MockLLMProvider 

331 provider = MockLLMProvider() 

332 

333 executor = _build_executor() 

334 router = ModelRouter.with_defaults(daily_budget_usd=50.0) 

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

336 

337 app = create_agent_api( 

338 provider=provider, 

339 executor=executor, 

340 router=router, 

341 cache=cache, 

342 config=ProductionConfig(budget_usd=50.0), 

343 ) 

344 

345 import uvicorn 

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

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

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

349 

350 

351def main(): 

352 args = sys.argv[1:] 

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

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

355 

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

357 from agentos import __version__ 

358 from agentos.cli.init import config_status_text 

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

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

361 print("Usage:") 

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

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

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

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

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

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

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

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

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

371 print(" agentos version Show version") 

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

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

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

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

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

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

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

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

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

381 print("\nOptions:") 

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

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

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

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

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

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

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

389 print("\nExamples:") 

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

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

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

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

394 print(" agentos demo") 

395 sys.exit(0) 

396 

397 cmd = args[0] 

398 if cmd == "init": 

399 from agentos.cli.init import init_cli 

400 init_cli(args) 

401 return 

402 

403 if cmd == "config-panel": 

404 from agentos.cli.config_panel import start_panel 

405 start_panel() 

406 return 

407 

408 if cmd == "status": 

409 from agentos.cli.init import _detect_current_config, config_status_text 

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

411 config = _detect_current_config() 

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

413 from agentos.cli.init import PROVIDERS 

414 p = PROVIDERS[name] 

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

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

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

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

419 return 

420 

421 if cmd == "version": 

422 from agentos import __version__ 

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

424 return 

425 

426 if cmd == "skills": 

427 from agentos.agents.market import AgentMarket 

428 market = AgentMarket() 

429 stats = market.stats() 

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

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

432 skills = market.list_by_category(cat) 

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

434 for s in skills: 

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

436 return 

437 

438 if cmd == "docs": 

439 from agentos.docs.generator import generate_api_docs, generate_quickstart 

440 import os 

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

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

443 sys.exit(1) 

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

445 os.makedirs(output_dir, exist_ok=True) 

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

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

448 md = generate_api_docs("agentos", api_path) 

449 generate_quickstart(qs_path) 

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

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

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

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

454 return 

455 

456 if cmd == "dashboard": 

457 from agentos.dashboard.server import start_dashboard 

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

459 start_dashboard() 

460 return 

461 

462 if cmd == "mcp-server": 

463 from agentos.mcp.server import start_mcp_server 

464 port = 0 

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

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

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

468 start_mcp_server(port=port) 

469 return 

470 

471 if cmd == "desktop": 

472 from agentos.desktop.server import launch_desktop 

473 host = "0.0.0.0" 

474 port = 19999 

475 mode = "dev" 

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

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

478 host = args[i + 2] 

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

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

481 elif a == "--safe": 

482 mode = "safe" 

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

484 return 

485 

486 if cmd == "desktop-shell": 

487 from agentos.desktop.shell import main as shell_main 

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

489 shell_main() 

490 return 

491 

492 if cmd == "enterprise": 

493 _run_enterprise(args[1:]) 

494 return 

495 

496 if cmd == "marketplace": 

497 _run_marketplace(args[1:]) 

498 return 

499 

500 if cmd == "rollback": 

501 from agentos.cli.rollback import rollback_cli 

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

503 

504 if cmd == "run": 

505 _run_production_agent(args[1:], verbose) 

506 return 

507 

508 if cmd == "serve": 

509 host = "0.0.0.0" 

510 port = 8080 

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

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

513 host = args[i + 2] 

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

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

516 from agentos.api.server import AgentAPI 

517 from agentos.core.loop import AgentLoop 

518 from agentos.core.context import ContextManager 

519 from agentos.tools.registry import ToolRegistry 

520 from agentos.models.router import ModelRouter, RECOMMENDED_CONFIG 

521 from agentos.tools.code_agent import CodeAgentTool, ShellTool 

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

523 from agentos.tools.web_tools import WebFetchTool 

524 registry = ToolRegistry() 

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

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

527 router = ModelRouter(RECOMMENDED_CONFIG) 

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

529 api = AgentAPI(loop) 

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

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

532 return 

533 

534 if cmd == "daemon": 

535 from agentos.server.daemon import daemon_main 

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

537 

538 if cmd == "hello": 

539 _run_hello() 

540 return 

541 

542 if cmd == "demo": 

543 print("=" * 60) 

544 print(" AgentOS — Interactive Demo") 

545 print("=" * 60) 

546 print() 

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

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

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

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

551 print() 

552 

553 try: 

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

555 except (EOFError, KeyboardInterrupt): 

556 choice = "1" 

557 

558 if choice == "3": 

559 _run_hello() 

560 elif choice == "2": 

561 _run_file_demo(verbose) 

562 else: 

563 _run_demo(verbose) 

564 return 

565 

566 # Run task 

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

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

569 if not task.strip(): 

570 no_task_provided() 

571 

572 agent = _build_agent(verbose=verbose) 

573 # 自动记录 tracker 

574 from agentos.dashboard.tracker import Tracker 

575 import uuid, time 

576 tracker = Tracker.get() 

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

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

579 t0 = time.time() 

580 try: 

581 result = agent.run(task) 

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

583 tracker.finish_session( 

584 session_id, 

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

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

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

588 ) 

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

590 if result.success: 

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

592 f"{result.total_steps} steps, " 

593 f"{result.total_tokens} tokens, " 

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

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

596 else: 

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

598 except Exception as e: 

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

600 raise 

601 

602 

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

604 """Skill Marketplace CLI dispatcher.""" 

605 

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

607 print("AgentOS Skill Marketplace\n") 

608 print("Subcommands:") 

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

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

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

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

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

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

615 print(" stats 市场统计") 

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

617 return 

618 

619 from agentos.marketplace import SkillRegistry 

620 

621 registry = SkillRegistry() 

622 sub = args[0] 

623 rest = args[1:] 

624 

625 if sub == "search": 

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

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

628 results = registry.search(query) 

629 if not results: 

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

631 return 

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

633 print("-" * 80) 

634 for r in results: 

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

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

637 

638 elif sub == "install": 

639 if not rest: 

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

641 return 

642 target = " ".join(rest) 

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

644 result = registry.install(target) 

645 if result.success and result.manifest: 

646 m = result.manifest 

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

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

649 if m.description: 

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

651 if result.dep_installed: 

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

653 if m.tools: 

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

655 else: 

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

657 

658 elif sub == "list": 

659 skills = registry.list_installed() 

660 if not skills: 

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

662 return 

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

664 print("-" * 90) 

665 for m in skills: 

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

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

668 

669 elif sub == "info": 

670 if not rest: 

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

672 return 

673 name = rest[0] 

674 m = registry.get_installed(name) 

675 if not m: 

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

677 return 

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

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

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

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

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

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

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

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

686 if m.entrypoint: 

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

688 if m.repository: 

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

690 if m.homepage: 

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

692 if m.tags: 

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

694 if m.tools: 

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

696 for t in m.tools: 

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

698 if m.dependencies: 

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

700 if m.mcp_command: 

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

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

703 if m.min_agentos_version: 

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

705 print() 

706 

707 elif sub == "update": 

708 if not rest: 

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

710 return 

711 name = rest[0] 

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

713 result = registry.update(name) 

714 if result.success and result.manifest: 

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

716 else: 

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

718 

719 elif sub == "uninstall": 

720 if not rest: 

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

722 return 

723 name = rest[0] 

724 if registry.uninstall(name): 

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

726 else: 

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

728 

729 elif sub == "stats": 

730 stats = registry.stats() 

731 print("Marketplace Stats:") 

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

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

734 if stats.get("by_format"): 

735 print(" By format:") 

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

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

738 

739 else: 

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

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

742 

743 

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

745 """Enterprise CLI dispatcher.""" 

746 

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

748 print("AgentOS Enterprise CLI\n") 

749 print("Subcommands:") 

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

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

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

753 return 

754 

755 sub = args[0] 

756 from agentos.enterprise import ( 

757 APIKeyManager, TenantManager, AuditLogger, 

758 ) 

759 

760 if sub == "api-key": 

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

762 elif sub == "tenant": 

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

764 elif sub == "audit": 

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

766 else: 

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

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

769 

770 

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

772 if not args: 

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

774 return 

775 cmd = args[0] 

776 if cmd == "create": 

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

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

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

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

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

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

783 elif cmd == "list": 

784 keys = mgr.list_keys() 

785 if not keys: 

786 print("No API keys.") 

787 return 

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

789 print("-" * 72) 

790 for k in keys: 

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

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

793 elif cmd == "revoke": 

794 if len(args) < 2: 

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

796 return 

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

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

799 elif cmd == "stats": 

800 stats = mgr.stats() 

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

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

803 else: 

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

805 

806 

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

808 if not args: 

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

810 return 

811 cmd = args[0] 

812 if cmd == "create": 

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

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

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

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

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

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

819 elif cmd == "list": 

820 tenants = mgr.list_tenants() 

821 if not tenants: 

822 print("No tenants.") 

823 return 

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

825 print("-" * 64) 

826 for t in tenants: 

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

828 elif cmd == "stats": 

829 stats = mgr.stats() 

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

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

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

833 else: 

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

835 

836 

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

838 if not args: 

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

840 return 

841 cmd = args[0] 

842 if cmd == "stats": 

843 stats = logger.stats() 

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

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

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

847 elif cmd == "recent": 

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

849 events = logger.get_recent(n) 

850 if not events: 

851 print("No audit events.") 

852 return 

853 for e in events: 

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

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

856 elif cmd == "export": 

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

858 if fmt == "csv": 

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

860 else: 

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

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

863 else: 

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

865 

866 

867def _run_demo(verbose: bool): 

868 from agentos.llm import create_provider, Tool, ToolParameter 

869 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig 

870 

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

872 data = { 

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

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

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

876 } 

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

878 

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

880 prices = { 

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

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

883 } 

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

885 

886 executor = ToolExecutor() 

887 executor.register( 

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

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

890 }), 

891 get_weather, 

892 ) 

893 executor.register( 

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

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

896 }), 

897 get_stock, 

898 ) 

899 

900 if os.getenv("OPENAI_API_KEY"): 

901 provider = create_provider("openai") 

902 elif os.getenv("DEEPSEEK_API_KEY"): 

903 provider = create_provider("deepseek") 

904 else: 

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

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

907 provider = MockLLMProvider([ 

908 MockLLMProvider.tool_response( 

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

910 ), 

911 MockLLMProvider.text_response( 

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

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

914 ), 

915 ]) 

916 

917 agent = ToolAgent( 

918 provider=provider, 

919 tool_executor=executor, 

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

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

922 ) 

923 

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

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

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

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

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

929 

930 

931if __name__ == "__main__": 

932 main()