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

606 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-07-02 15:46 +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 docs [output-dir] Generate API reference docs (→ docs/api/)") 

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

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

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

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

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

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

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

330 print("\nOptions:") 

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

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

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

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

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

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

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

338 print("\nExamples:") 

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

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

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

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

343 print(" agentos demo") 

344 sys.exit(0) 

345 

346 cmd = args[0] 

347 if cmd == "init": 

348 from agentos.cli.init import init_cli 

349 init_cli(args) 

350 return 

351 

352 if cmd == "config-panel": 

353 from agentos.cli.config_panel import start_panel 

354 start_panel() 

355 return 

356 

357 if cmd == "status": 

358 from agentos.cli.init import _detect_current_config, config_status_text 

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

360 config = _detect_current_config() 

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

362 from agentos.cli.init import PROVIDERS 

363 p = PROVIDERS[name] 

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

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

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

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

368 return 

369 

370 if cmd == "version": 

371 from agentos import __version__ 

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

373 return 

374 

375 if cmd == "skills": 

376 from agentos.agents.market import AgentMarket 

377 market = AgentMarket() 

378 stats = market.stats() 

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

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

381 skills = market.list_by_category(cat) 

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

383 for s in skills: 

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

385 return 

386 

387 if cmd == "docs": 

388 from agentos.docs.generator import generate_api_docs, generate_quickstart 

389 import os 

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

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

392 sys.exit(1) 

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

394 os.makedirs(output_dir, exist_ok=True) 

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

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

397 md = generate_api_docs("agentos", api_path) 

398 generate_quickstart(qs_path) 

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

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

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

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

403 return 

404 

405 if cmd == "dashboard": 

406 from agentos.dashboard.server import start_dashboard 

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

408 start_dashboard() 

409 return 

410 

411 if cmd == "mcp-server": 

412 from agentos.mcp.server import start_mcp_server 

413 port = 0 

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

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

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

417 start_mcp_server(port=port) 

418 return 

419 

420 if cmd == "desktop": 

421 from agentos.desktop.server import launch_desktop 

422 host = "0.0.0.0" 

423 port = 19999 

424 mode = "dev" 

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

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

427 host = args[i + 2] 

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

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

430 elif a == "--safe": 

431 mode = "safe" 

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

433 return 

434 

435 if cmd == "desktop-shell": 

436 from agentos.desktop.shell import main as shell_main 

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

438 shell_main() 

439 return 

440 

441 if cmd == "enterprise": 

442 _run_enterprise(args[1:]) 

443 return 

444 

445 if cmd == "marketplace": 

446 _run_marketplace(args[1:]) 

447 return 

448 

449 if cmd == "rollback": 

450 from agentos.cli.rollback import rollback_cli 

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

452 

453 if cmd == "serve": 

454 host = "0.0.0.0" 

455 port = 8080 

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

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

458 host = args[i + 2] 

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

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

461 from agentos.api.server import AgentAPI 

462 from agentos.core.loop import AgentLoop, LoopConfig 

463 from agentos.core.context import ContextManager 

464 from agentos.tools.registry import ToolRegistry 

465 from agentos.models.router import ModelRouter, RECOMMENDED_CONFIG 

466 from agentos.tools.code_agent import CodeAgentTool, ShellTool 

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

468 from agentos.tools.web_tools import WebFetchTool 

469 registry = ToolRegistry() 

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

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

472 router = ModelRouter(RECOMMENDED_CONFIG) 

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

474 api = AgentAPI(loop) 

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

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

477 return 

478 

479 if cmd == "daemon": 

480 from agentos.server.daemon import daemon_main 

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

482 

483 if cmd == "hello": 

484 _run_hello() 

485 return 

486 

487 if cmd == "demo": 

488 print("=" * 60) 

489 print(" AgentOS — Interactive Demo") 

490 print("=" * 60) 

491 print() 

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

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

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

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

496 print() 

497 

498 try: 

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

500 except (EOFError, KeyboardInterrupt): 

501 choice = "1" 

502 

503 if choice == "3": 

504 _run_hello() 

505 elif choice == "2": 

506 _run_file_demo(verbose) 

507 else: 

508 _run_demo(verbose) 

509 return 

510 

511 # Run task 

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

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

514 if not task.strip(): 

515 no_task_provided() 

516 

517 agent = _build_agent(verbose=verbose) 

518 # 自动记录 tracker 

519 from agentos.dashboard.tracker import Tracker 

520 import uuid, time 

521 tracker = Tracker.get() 

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

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

524 t0 = time.time() 

525 try: 

526 result = agent.run(task) 

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

528 tracker.finish_session( 

529 session_id, 

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

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

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

533 ) 

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

535 if result.success: 

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

537 f"{result.total_steps} steps, " 

538 f"{result.total_tokens} tokens, " 

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

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

541 else: 

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

543 except Exception as e: 

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

545 raise 

546 

547 

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

549 """Skill Marketplace CLI dispatcher.""" 

550 

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

552 print("AgentOS Skill Marketplace\n") 

553 print("Subcommands:") 

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

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

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

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

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

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

560 print(" stats 市场统计") 

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

562 return 

563 

564 from agentos.marketplace import SkillRegistry, InstallResult 

565 

566 registry = SkillRegistry() 

567 sub = args[0] 

568 rest = args[1:] 

569 

570 if sub == "search": 

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

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

573 results = registry.search(query) 

574 if not results: 

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

576 return 

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

578 print("-" * 80) 

579 for r in results: 

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

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

582 

583 elif sub == "install": 

584 if not rest: 

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

586 return 

587 target = " ".join(rest) 

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

589 result = registry.install(target) 

590 if result.success and result.manifest: 

591 m = result.manifest 

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

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

594 if m.description: 

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

596 if result.dep_installed: 

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

598 if m.tools: 

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

600 else: 

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

602 

603 elif sub == "list": 

604 skills = registry.list_installed() 

605 if not skills: 

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

607 return 

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

609 print("-" * 90) 

610 for m in skills: 

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

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

613 

614 elif sub == "info": 

615 if not rest: 

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

617 return 

618 name = rest[0] 

619 m = registry.get_installed(name) 

620 if not m: 

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

622 return 

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

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

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

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

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

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

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

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

631 if m.entrypoint: 

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

633 if m.repository: 

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

635 if m.homepage: 

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

637 if m.tags: 

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

639 if m.tools: 

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

641 for t in m.tools: 

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

643 if m.dependencies: 

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

645 if m.mcp_command: 

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

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

648 if m.min_agentos_version: 

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

650 print() 

651 

652 elif sub == "update": 

653 if not rest: 

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

655 return 

656 name = rest[0] 

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

658 result = registry.update(name) 

659 if result.success and result.manifest: 

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

661 else: 

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

663 

664 elif sub == "uninstall": 

665 if not rest: 

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

667 return 

668 name = rest[0] 

669 if registry.uninstall(name): 

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

671 else: 

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

673 

674 elif sub == "stats": 

675 stats = registry.stats() 

676 print(f"Marketplace Stats:") 

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

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

679 if stats.get("by_format"): 

680 print(f" By format:") 

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

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

683 

684 else: 

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

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

687 

688 

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

690 """Enterprise CLI dispatcher.""" 

691 

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

693 print("AgentOS Enterprise CLI\n") 

694 print("Subcommands:") 

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

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

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

698 return 

699 

700 sub = args[0] 

701 from agentos.enterprise import ( 

702 APIKeyManager, TenantManager, AuditLogger, 

703 KeyCreateRequest, KeyScope, TenantTier, 

704 ) 

705 

706 if sub == "api-key": 

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

708 elif sub == "tenant": 

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

710 elif sub == "audit": 

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

712 else: 

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

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

715 

716 

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

718 if not args: 

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

720 return 

721 cmd = args[0] 

722 if cmd == "create": 

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

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

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

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

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

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

729 elif cmd == "list": 

730 keys = mgr.list_keys() 

731 if not keys: 

732 print("No API keys.") 

733 return 

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

735 print("-" * 72) 

736 for k in keys: 

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

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

739 elif cmd == "revoke": 

740 if len(args) < 2: 

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

742 return 

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

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

745 elif cmd == "stats": 

746 stats = mgr.stats() 

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

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

749 else: 

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

751 

752 

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

754 if not args: 

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

756 return 

757 cmd = args[0] 

758 if cmd == "create": 

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

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

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

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

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

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

765 elif cmd == "list": 

766 tenants = mgr.list_tenants() 

767 if not tenants: 

768 print("No tenants.") 

769 return 

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

771 print("-" * 64) 

772 for t in tenants: 

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

774 elif cmd == "stats": 

775 stats = mgr.stats() 

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

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

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

779 else: 

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

781 

782 

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

784 if not args: 

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

786 return 

787 cmd = args[0] 

788 if cmd == "stats": 

789 stats = logger.stats() 

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

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

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

793 elif cmd == "recent": 

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

795 events = logger.get_recent(n) 

796 if not events: 

797 print("No audit events.") 

798 return 

799 for e in events: 

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

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

802 elif cmd == "export": 

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

804 if fmt == "csv": 

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

806 else: 

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

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

809 else: 

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

811 

812 

813def _run_demo(verbose: bool): 

814 from agentos.llm import create_provider, Tool, ToolParameter 

815 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig 

816 

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

818 data = { 

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

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

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

822 } 

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

824 

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

826 prices = { 

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

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

829 } 

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

831 

832 executor = ToolExecutor() 

833 executor.register( 

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

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

836 }), 

837 get_weather, 

838 ) 

839 executor.register( 

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

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

842 }), 

843 get_stock, 

844 ) 

845 

846 if os.getenv("OPENAI_API_KEY"): 

847 provider = create_provider("openai") 

848 elif os.getenv("DEEPSEEK_API_KEY"): 

849 provider = create_provider("deepseek") 

850 else: 

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

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

853 provider = MockLLMProvider([ 

854 MockLLMProvider.tool_response( 

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

856 ), 

857 MockLLMProvider.text_response( 

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

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

860 ), 

861 ]) 

862 

863 agent = ToolAgent( 

864 provider=provider, 

865 tool_executor=executor, 

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

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

868 ) 

869 

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

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

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

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

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

875 

876 

877if __name__ == "__main__": 

878 main()