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

631 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-04 16:20 +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 main(): 

302 args = sys.argv[1:] 

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

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

305 

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

307 from agentos import __version__ 

308 from agentos.cli.init import config_status_text 

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

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

311 print("Usage:") 

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

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

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

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

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

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

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

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

320 print(" agentos version Show version") 

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

322 print(" agentos tools List all available tools (base + skills)") 

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

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

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

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

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

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

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

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

331 print("\nOptions:") 

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

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

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

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

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

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

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

339 print("\nExamples:") 

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

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

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

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

344 print(" agentos demo") 

345 sys.exit(0) 

346 

347 cmd = args[0] 

348 if cmd == "init": 

349 from agentos.cli.init import init_cli 

350 init_cli(args) 

351 return 

352 

353 if cmd == "config-panel": 

354 from agentos.cli.config_panel import start_panel 

355 start_panel() 

356 return 

357 

358 if cmd == "status": 

359 from agentos.cli.init import _detect_current_config, config_status_text 

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

361 config = _detect_current_config() 

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

363 from agentos.cli.init import PROVIDERS 

364 p = PROVIDERS[name] 

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

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

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

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

369 return 

370 

371 if cmd == "version": 

372 from agentos import __version__ 

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

374 return 

375 

376 if cmd == "skills": 

377 from agentos.agents.market import AgentMarket 

378 market = AgentMarket() 

379 stats = market.stats() 

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

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

382 skills = market.list_by_category(cat) 

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

384 for s in skills: 

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

386 return 

387 

388 if cmd == "tools": 

389 print("AgentOS — Available Tools\n") 

390 print("Base Tools:") 

391 try: 

392 from agentos.agent.agent_builder import discover_tools 

393 tools = discover_tools() 

394 for t in tools: 

395 print(f" {t.name:25s} {t.description[:70]}") 

396 print(f"\n → {len(tools)} base tools") 

397 except Exception as e: 

398 print(f" Error: {e}") 

399 print() 

400 print("Skills (auto-discovered from marketplace/skills):") 

401 try: 

402 from agentos.tools.skill_tool import discover_skills 

403 skills = discover_skills() 

404 for s in skills[:20]: 

405 print(f" {s.name:30s} {s.description[:60]}") 

406 if len(skills) > 20: 

407 print(f" ... and {len(skills)-20} more") 

408 print(f"\n → {len(skills)} skills as tools") 

409 except Exception as e: 

410 print(f" Error: {e}") 

411 return 

412 

413 if cmd == "docs": 

414 from agentos.docs.generator import generate_api_docs, generate_quickstart 

415 import os 

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

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

418 sys.exit(1) 

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

420 os.makedirs(output_dir, exist_ok=True) 

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

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

423 md = generate_api_docs("agentos", api_path) 

424 generate_quickstart(qs_path) 

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

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

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

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

429 return 

430 

431 if cmd == "dashboard": 

432 from agentos.dashboard.server import start_dashboard 

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

434 start_dashboard() 

435 return 

436 

437 if cmd == "mcp-server": 

438 from agentos.mcp.server import start_mcp_server 

439 port = 0 

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

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

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

443 start_mcp_server(port=port) 

444 return 

445 

446 if cmd == "desktop": 

447 from agentos.desktop.server import launch_desktop 

448 host = "0.0.0.0" 

449 port = 19999 

450 mode = "dev" 

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

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

453 host = args[i + 2] 

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

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

456 elif a == "--safe": 

457 mode = "safe" 

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

459 return 

460 

461 if cmd == "desktop-shell": 

462 from agentos.desktop.shell import main as shell_main 

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

464 shell_main() 

465 return 

466 

467 if cmd == "enterprise": 

468 _run_enterprise(args[1:]) 

469 return 

470 

471 if cmd == "marketplace": 

472 _run_marketplace(args[1:]) 

473 return 

474 

475 if cmd == "rollback": 

476 from agentos.cli.rollback import rollback_cli 

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

478 

479 if cmd == "serve": 

480 host = "0.0.0.0" 

481 port = 8080 

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

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

484 host = args[i + 2] 

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

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

487 from agentos.api.server import AgentAPI 

488 from agentos.core.loop import AgentLoop, LoopConfig 

489 from agentos.core.context import ContextManager 

490 from agentos.tools.registry import ToolRegistry 

491 from agentos.models.router import ModelRouter, RECOMMENDED_CONFIG 

492 from agentos.tools.code_agent import CodeAgentTool, ShellTool 

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

494 from agentos.tools.web_tools import WebFetchTool 

495 registry = ToolRegistry() 

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

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

498 router = ModelRouter(RECOMMENDED_CONFIG) 

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

500 api = AgentAPI(loop) 

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

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

503 return 

504 

505 if cmd == "daemon": 

506 from agentos.server.daemon import daemon_main 

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

508 

509 if cmd == "hello": 

510 _run_hello() 

511 return 

512 

513 if cmd == "demo": 

514 print("=" * 60) 

515 print(" AgentOS — Interactive Demo") 

516 print("=" * 60) 

517 print() 

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

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

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

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

522 print() 

523 

524 try: 

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

526 except (EOFError, KeyboardInterrupt): 

527 choice = "1" 

528 

529 if choice == "3": 

530 _run_hello() 

531 elif choice == "2": 

532 _run_file_demo(verbose) 

533 else: 

534 _run_demo(verbose) 

535 return 

536 

537 # Run task 

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

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

540 if not task.strip(): 

541 no_task_provided() 

542 

543 agent = _build_agent(verbose=verbose) 

544 # 自动记录 tracker 

545 from agentos.dashboard.tracker import Tracker 

546 import uuid, time 

547 tracker = Tracker.get() 

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

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

550 t0 = time.time() 

551 try: 

552 result = agent.run(task) 

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

554 tracker.finish_session( 

555 session_id, 

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

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

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

559 ) 

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

561 if result.success: 

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

563 f"{result.total_steps} steps, " 

564 f"{result.total_tokens} tokens, " 

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

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

567 else: 

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

569 except Exception as e: 

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

571 raise 

572 

573 

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

575 """Skill Marketplace CLI dispatcher.""" 

576 

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

578 print("AgentOS Skill Marketplace\n") 

579 print("Subcommands:") 

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

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

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

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

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

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

586 print(" stats 市场统计") 

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

588 return 

589 

590 from agentos.marketplace import SkillRegistry, InstallResult 

591 

592 registry = SkillRegistry() 

593 sub = args[0] 

594 rest = args[1:] 

595 

596 if sub == "search": 

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

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

599 results = registry.search(query) 

600 if not results: 

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

602 return 

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

604 print("-" * 80) 

605 for r in results: 

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

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

608 

609 elif sub == "install": 

610 if not rest: 

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

612 return 

613 target = " ".join(rest) 

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

615 result = registry.install(target) 

616 if result.success and result.manifest: 

617 m = result.manifest 

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

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

620 if m.description: 

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

622 if result.dep_installed: 

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

624 if m.tools: 

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

626 else: 

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

628 

629 elif sub == "list": 

630 skills = registry.list_installed() 

631 if not skills: 

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

633 return 

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

635 print("-" * 90) 

636 for m in skills: 

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

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

639 

640 elif sub == "info": 

641 if not rest: 

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

643 return 

644 name = rest[0] 

645 m = registry.get_installed(name) 

646 if not m: 

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

648 return 

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

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

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

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

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

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

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

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

657 if m.entrypoint: 

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

659 if m.repository: 

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

661 if m.homepage: 

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

663 if m.tags: 

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

665 if m.tools: 

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

667 for t in m.tools: 

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

669 if m.dependencies: 

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

671 if m.mcp_command: 

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

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

674 if m.min_agentos_version: 

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

676 print() 

677 

678 elif sub == "update": 

679 if not rest: 

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

681 return 

682 name = rest[0] 

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

684 result = registry.update(name) 

685 if result.success and result.manifest: 

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

687 else: 

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

689 

690 elif sub == "uninstall": 

691 if not rest: 

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

693 return 

694 name = rest[0] 

695 if registry.uninstall(name): 

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

697 else: 

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

699 

700 elif sub == "stats": 

701 stats = registry.stats() 

702 print(f"Marketplace Stats:") 

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

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

705 if stats.get("by_format"): 

706 print(f" By format:") 

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

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

709 

710 else: 

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

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

713 

714 

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

716 """Enterprise CLI dispatcher.""" 

717 

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

719 print("AgentOS Enterprise CLI\n") 

720 print("Subcommands:") 

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

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

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

724 return 

725 

726 sub = args[0] 

727 from agentos.enterprise import ( 

728 APIKeyManager, TenantManager, AuditLogger, 

729 KeyCreateRequest, KeyScope, TenantTier, 

730 ) 

731 

732 if sub == "api-key": 

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

734 elif sub == "tenant": 

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

736 elif sub == "audit": 

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

738 else: 

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

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

741 

742 

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

744 if not args: 

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

746 return 

747 cmd = args[0] 

748 if cmd == "create": 

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

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

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

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

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

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

755 elif cmd == "list": 

756 keys = mgr.list_keys() 

757 if not keys: 

758 print("No API keys.") 

759 return 

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

761 print("-" * 72) 

762 for k in keys: 

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

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

765 elif cmd == "revoke": 

766 if len(args) < 2: 

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

768 return 

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

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

771 elif cmd == "stats": 

772 stats = mgr.stats() 

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

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

775 else: 

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

777 

778 

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

780 if not args: 

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

782 return 

783 cmd = args[0] 

784 if cmd == "create": 

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

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

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

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

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

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

791 elif cmd == "list": 

792 tenants = mgr.list_tenants() 

793 if not tenants: 

794 print("No tenants.") 

795 return 

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

797 print("-" * 64) 

798 for t in tenants: 

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

800 elif cmd == "stats": 

801 stats = mgr.stats() 

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

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

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

805 else: 

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

807 

808 

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

810 if not args: 

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

812 return 

813 cmd = args[0] 

814 if cmd == "stats": 

815 stats = logger.stats() 

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

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

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

819 elif cmd == "recent": 

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

821 events = logger.get_recent(n) 

822 if not events: 

823 print("No audit events.") 

824 return 

825 for e in events: 

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

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

828 elif cmd == "export": 

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

830 if fmt == "csv": 

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

832 else: 

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

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

835 else: 

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

837 

838 

839def _run_demo(verbose: bool): 

840 from agentos.llm import create_provider, Tool, ToolParameter 

841 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig 

842 

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

844 data = { 

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

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

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

848 } 

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

850 

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

852 prices = { 

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

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

855 } 

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

857 

858 executor = ToolExecutor() 

859 executor.register( 

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

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

862 }), 

863 get_weather, 

864 ) 

865 executor.register( 

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

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

868 }), 

869 get_stock, 

870 ) 

871 

872 if os.getenv("OPENAI_API_KEY"): 

873 provider = create_provider("openai") 

874 elif os.getenv("DEEPSEEK_API_KEY"): 

875 provider = create_provider("deepseek") 

876 else: 

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

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

879 provider = MockLLMProvider([ 

880 MockLLMProvider.tool_response( 

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

882 ), 

883 MockLLMProvider.text_response( 

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

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

886 ), 

887 ]) 

888 

889 agent = ToolAgent( 

890 provider=provider, 

891 tool_executor=executor, 

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

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

894 ) 

895 

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

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

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

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

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

901 

902 

903if __name__ == "__main__": 

904 main()