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
« 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"""
5from __future__ import annotations
7import os
8import sys
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)
20def _build_executor() -> ToolExecutor:
21 executor = ToolExecutor()
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 )
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 )
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 )
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 )
85 return executor
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}"
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}"
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}"
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}"
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()
150 config = AgentConfig(verbose=verbose)
151 executor = _build_executor()
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 )
170def _run_hello():
171 """一键 hello world 体验 — 无需配置,始终可用。"""
172 from agentos import __version__
173 import time
175 print(f" \033[36mNexus AgentOS\033[0m v{__version__}")
176 print(" \033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
177 print()
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"
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 ]
203 for label, status in steps:
204 time.sleep(0.12)
205 print(f" \033[2m▸\033[0m {label:<12s} {status}")
207 time.sleep(0.3)
208 print()
209 print(" \033[1m一切就绪。\033[0m")
210 print()
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()
223def _run_file_demo(verbose: bool):
224 """文件操作演示 — 创建、读取、列表。"""
225 from agentos.agent.tool_agent import ToolAgent, ToolExecutor, AgentConfig
227 executor = ToolExecutor()
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}"
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}"
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}"
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 )
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
282 agent = ToolAgent(
283 provider=provider,
284 tool_executor=executor,
285 config=AgentConfig(verbose=verbose, temperature=0.0),
286 system_prompt="你是文件操作助手。用工具完成任务后用中文汇报结果。",
287 )
289 demo_dir = "/tmp/agentos_file_demo"
290 print(f"\n 演示目录: {demo_dir}")
291 print()
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}")
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
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
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)
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()
333 executor = _build_executor()
334 router = ModelRouter.with_defaults(daily_budget_usd=50.0)
335 cache = SmartCache(CacheConfig(enabled=True, fuzzy_enabled=True))
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 )
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")
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")]
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)
397 cmd = args[0]
398 if cmd == "init":
399 from agentos.cli.init import init_cli
400 init_cli(args)
401 return
403 if cmd == "config-panel":
404 from agentos.cli.config_panel import start_panel
405 start_panel()
406 return
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
421 if cmd == "version":
422 from agentos import __version__
423 print(f"AgentOS v{__version__}")
424 return
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
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
456 if cmd == "dashboard":
457 from agentos.dashboard.server import start_dashboard
458 print("Starting AgentOS Dashboard...")
459 start_dashboard()
460 return
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
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
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
492 if cmd == "enterprise":
493 _run_enterprise(args[1:])
494 return
496 if cmd == "marketplace":
497 _run_marketplace(args[1:])
498 return
500 if cmd == "rollback":
501 from agentos.cli.rollback import rollback_cli
502 sys.exit(rollback_cli(args[1:]))
504 if cmd == "run":
505 _run_production_agent(args[1:], verbose)
506 return
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
534 if cmd == "daemon":
535 from agentos.server.daemon import daemon_main
536 sys.exit(daemon_main(args[1:]))
538 if cmd == "hello":
539 _run_hello()
540 return
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()
553 try:
554 choice = input(" 请输入数字 (1-3) [1]: ").strip() or "1"
555 except (EOFError, KeyboardInterrupt):
556 choice = "1"
558 if choice == "3":
559 _run_hello()
560 elif choice == "2":
561 _run_file_demo(verbose)
562 else:
563 _run_demo(verbose)
564 return
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()
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
603def _run_marketplace(args: list[str]):
604 """Skill Marketplace CLI dispatcher."""
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
619 from agentos.marketplace import SkillRegistry
621 registry = SkillRegistry()
622 sub = args[0]
623 rest = args[1:]
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}")
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}")
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}")
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()
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}")
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.")
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}")
739 else:
740 print(f"Unknown marketplace command: {sub}")
741 print("Try: agentos marketplace --help")
744def _run_enterprise(args: list[str]):
745 """Enterprise CLI dispatcher."""
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
755 sub = args[0]
756 from agentos.enterprise import (
757 APIKeyManager, TenantManager, AuditLogger,
758 )
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")
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}")
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}")
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}")
867def _run_demo(verbose: bool):
868 from agentos.llm import create_provider, Tool, ToolParameter
869 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig
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}: 数据暂缺")
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}: 未找到")
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 )
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 ])
917 agent = ToolAgent(
918 provider=provider,
919 tool_executor=executor,
920 config=AgentConfig(verbose=verbose, temperature=0.0),
921 system_prompt="你是一个天气助手。用工具获取天气/股票信息后直接回答。",
922 )
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}")
931if __name__ == "__main__":
932 main()