Coverage for agentos/cli/main.py: 4%
646 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 08:01 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 08:01 +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 asyncio
8import os
9import sys
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)
22def _build_executor() -> ToolExecutor:
23 executor = ToolExecutor()
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 )
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 )
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 )
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 )
87 return executor
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}"
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}"
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}"
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}"
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()
152 config = AgentConfig(verbose=verbose)
153 executor = _build_executor()
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 )
172def _run_hello():
173 """一键 hello world 体验 — 无需配置,始终可用。"""
174 from agentos import __version__
175 import time
177 print(f" \033[36mNexus AgentOS\033[0m v{__version__}")
178 print(f" \033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
179 print()
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"
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 ]
205 for label, status in steps:
206 time.sleep(0.12)
207 print(f" \033[2m▸\033[0m {label:<12s} {status}")
209 time.sleep(0.3)
210 print()
211 print(f" \033[1m一切就绪。\033[0m")
212 print()
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()
225def _run_file_demo(verbose: bool):
226 """文件操作演示 — 创建、读取、列表。"""
227 from agentos.agent.tool_agent import ToolAgent, ToolExecutor, AgentConfig
229 executor = ToolExecutor()
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}"
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}"
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}"
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 )
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
284 agent = ToolAgent(
285 provider=provider,
286 tool_executor=executor,
287 config=AgentConfig(verbose=verbose, temperature=0.0),
288 system_prompt="你是文件操作助手。用工具完成任务后用中文汇报结果。",
289 )
291 demo_dir = "/tmp/agentos_file_demo"
292 print(f"\n 演示目录: {demo_dir}")
293 print()
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}")
301def _run_production_agent(cli_args: list[str], verbose: bool = False) -> None:
302 """Run a task through ProductionAgent (ModelRouter + AuditLogger + SmartCache)."""
303 host = "0.0.0.0"
304 port = 8090
306 i = 0
307 while i < len(cli_args):
308 a = cli_args[i]
309 if a == "--host" and i + 1 < len(cli_args):
310 host = cli_args[i + 1]; i += 2; continue
311 elif a == "--port" and i + 1 < len(cli_args):
312 port = int(cli_args[i + 1]); i += 2; continue
313 elif a == "--budget" and i + 1 < len(cli_args):
314 i += 2; continue
315 else:
316 i += 1
318 try:
319 from agentos.llm.factory import create_provider
320 from agentos.agent.model_router import ModelRouter
321 from agentos.llm.smart_cache import SmartCache, CacheConfig
322 from agentos.agent.production import ProductionConfig
323 from agentos.server.agent_api import create_agent_api
324 except ImportError as e:
325 print(f"Error: missing dependency — {e}")
326 sys.exit(1)
328 try:
329 provider = create_provider("openai")
330 except Exception:
331 print("Warning: no live provider, using mock.")
332 from agentos.agent.tool_agent import MockLLMProvider
333 provider = MockLLMProvider()
335 executor = _build_executor()
336 router = ModelRouter.with_defaults(daily_budget_usd=50.0)
337 cache = SmartCache(CacheConfig(enabled=True, fuzzy_enabled=True))
339 app = create_agent_api(
340 provider=provider,
341 executor=executor,
342 router=router,
343 cache=cache,
344 config=ProductionConfig(budget_usd=50.0),
345 )
347 import uvicorn
348 print(f"AgentOS Production API → http://{host}:{port}")
349 print(f" POST /agent/run GET /agent/health GET /agent/stats")
350 uvicorn.run(app, host=host, port=port, log_level="info" if verbose else "warning")
353def main():
354 args = sys.argv[1:]
355 verbose = "--verbose" in args or "-v" in args
356 args = [a for a in args if a not in ("--verbose", "-v")]
358 if not args or args[0] in ("help", "--help", "-h"):
359 from agentos import __version__
360 from agentos.cli.init import config_status_text
361 print(f"AgentOS v{__version__} — Production Agent Framework CLI\n")
362 print(f"Provider: {config_status_text()}\n")
363 print("Usage:")
364 print(" agentos init Interactive setup wizard (recommended)")
365 print(" agentos hello One-step health check & quick intro")
366 print(" agentos config-panel Open web config panel in browser")
367 print(" agentos <task> Run a task with the autonomous agent")
368 print(" agentos run <task> Same as above")
369 print(" agentos demo Run interactive demo (weather/stock/files)")
370 print(" agentos serve Start API server (port 8080)")
371 print(" agentos run [--port PORT] Start ProductionAgent API (port 8090)")
372 print(" agentos daemon <cmd> Server daemon: start|stop|status|restart|run")
373 print(" agentos version Show version")
374 print(" agentos skills List agent marketplace skills")
375 print(" agentos docs [output-dir] Generate API reference docs (→ docs/api/)")
376 print(" agentos dashboard Open web trace dashboard (port 18500)")
377 print(" agentos mcp-server Start MCP server (stdio) for Claude Desktop etc.")
378 print(" agentos desktop Launch web desktop client (port 19999)")
379 print(" agentos desktop-shell Launch native desktop shell (pywebview)")
380 print(" agentos enterprise <cmd> Enterprise features: api-key, tenant, audit")
381 print(" agentos marketplace <cmd> Skill marketplace: search|install|list|update|uninstall|stats")
382 print(" agentos rollback <version> Rollback to a previous version (--list|--verify|--prune)")
383 print("\nOptions:")
384 print(" -v, --verbose Show agent step details")
385 print(" --provider <name> Force provider: openai|deepseek|anthropic")
386 print("\nProvider (auto-detect via env vars):")
387 print(" OPENAI_API_KEY → OpenAI (gpt-4o-mini)")
388 print(" DEEPSEEK_API_KEY → DeepSeek (deepseek-chat)")
389 print(" ANTHROPIC_API_KEY → Anthropic (claude-sonnet-4)")
390 print(" (none set) → Mock demo mode — run 'agentos init' to configure")
391 print("\nExamples:")
392 print(" agentos hello # 30s quick tour")
393 print(" agentos init # 1-minute setup wizard")
394 print(" agentos \"列出当前目录的文件\"")
395 print(" agentos \"创建一个 hello.py 打印 Hello World\"")
396 print(" agentos demo")
397 sys.exit(0)
399 cmd = args[0]
400 if cmd == "init":
401 from agentos.cli.init import init_cli
402 init_cli(args)
403 return
405 if cmd == "config-panel":
406 from agentos.cli.config_panel import start_panel
407 start_panel()
408 return
410 if cmd == "status":
411 from agentos.cli.init import _detect_current_config, config_status_text
412 print(f"Provider: {config_status_text()}\n")
413 config = _detect_current_config()
414 for name, info in config["providers"].items():
415 from agentos.cli.init import PROVIDERS
416 p = PROVIDERS[name]
417 status_icon = "✅" if info["env_set"] else "⬜"
418 key_info = info.get("key_preview", "未配置") or "未配置"
419 in_config = " (配置文件)" if info.get("in_config") else ""
420 print(f" {status_icon} {p['label']:20s} {key_info:25s}{in_config}")
421 return
423 if cmd == "version":
424 from agentos import __version__
425 print(f"AgentOS v{__version__}")
426 return
428 if cmd == "skills":
429 from agentos.agents.market import AgentMarket
430 market = AgentMarket()
431 stats = market.stats()
432 print(f"Agent Skill Market: {stats['total']} skills\n")
433 for cat, count in stats["by_category"].items():
434 skills = market.list_by_category(cat)
435 print(f" [{cat}] ({count})")
436 for s in skills:
437 print(f" {s.name}: {s.description}")
438 return
440 if cmd == "docs":
441 from agentos.docs.generator import generate_api_docs, generate_quickstart
442 import os
443 if not os.path.isdir("agentos"):
444 print("Error: run 'agentos docs' from the agentos project root directory")
445 sys.exit(1)
446 output_dir = args[1] if len(args) > 1 else "docs"
447 os.makedirs(output_dir, exist_ok=True)
448 api_path = os.path.join(output_dir, "api_reference.md")
449 qs_path = os.path.join(output_dir, "quickstart.md")
450 md = generate_api_docs("agentos", api_path)
451 generate_quickstart(qs_path)
452 module_count = len([d for d in os.listdir("agentos") if os.path.isdir(os.path.join("agentos", d))])
453 print(f"Generated API docs: {api_path} ({len(md.splitlines())} lines)")
454 print(f"Generated Quickstart: {qs_path}")
455 print(f"Scanned ~{module_count} source modules")
456 return
458 if cmd == "dashboard":
459 from agentos.dashboard.server import start_dashboard
460 print("Starting AgentOS Dashboard...")
461 start_dashboard()
462 return
464 if cmd == "mcp-server":
465 from agentos.mcp.server import start_mcp_server
466 port = 0
467 for i, a in enumerate(args[1:]):
468 if a == "--port" and i + 2 < len(args):
469 port = int(args[i + 2])
470 start_mcp_server(port=port)
471 return
473 if cmd == "desktop":
474 from agentos.desktop.server import launch_desktop
475 host = "0.0.0.0"
476 port = 19999
477 mode = "dev"
478 for i, a in enumerate(args[1:]):
479 if a == "--host" and i + 2 < len(args):
480 host = args[i + 2]
481 elif a == "--port" and i + 2 < len(args):
482 port = int(args[i + 2])
483 elif a == "--safe":
484 mode = "safe"
485 launch_desktop(host=host, port=port, mode=mode)
486 return
488 if cmd == "desktop-shell":
489 from agentos.desktop.shell import main as shell_main
490 sys.argv = [sys.argv[0]] + args[1:]
491 shell_main()
492 return
494 if cmd == "enterprise":
495 _run_enterprise(args[1:])
496 return
498 if cmd == "marketplace":
499 _run_marketplace(args[1:])
500 return
502 if cmd == "rollback":
503 from agentos.cli.rollback import rollback_cli
504 sys.exit(rollback_cli(args[1:]))
506 if cmd == "run":
507 _run_production_agent(args[1:], verbose)
508 return
510 if cmd == "serve":
511 host = "0.0.0.0"
512 port = 8080
513 for i, a in enumerate(args[1:]):
514 if a == "--host" and i + 2 < len(args):
515 host = args[i + 2]
516 elif a == "--port" and i + 2 < len(args):
517 port = int(args[i + 2])
518 from agentos.api.server import AgentAPI
519 from agentos.core.loop import AgentLoop, LoopConfig
520 from agentos.core.context import ContextManager
521 from agentos.tools.registry import ToolRegistry
522 from agentos.models.router import ModelRouter, RECOMMENDED_CONFIG
523 from agentos.tools.code_agent import CodeAgentTool, ShellTool
524 from agentos.tools.file_tools import ReadFileTool, WriteFileTool, ListDirectoryTool
525 from agentos.tools.web_tools import WebFetchTool
526 registry = ToolRegistry()
527 registry.register_many([ReadFileTool(), WriteFileTool(), ListDirectoryTool(), CodeAgentTool(), ShellTool(), WebFetchTool()])
528 ctx = ContextManager(system_prompt="AgentOS API Server v1.0")
529 router = ModelRouter(RECOMMENDED_CONFIG)
530 loop = AgentLoop(model_router=router, tool_registry=registry, context_manager=ctx)
531 api = AgentAPI(loop)
532 print(f"AgentOS API starting on http://{host}:{port}")
533 api.serve(host=host, port=port)
534 return
536 if cmd == "daemon":
537 from agentos.server.daemon import daemon_main
538 sys.exit(daemon_main(args[1:]))
540 if cmd == "hello":
541 _run_hello()
542 return
544 if cmd == "demo":
545 print("=" * 60)
546 print(" AgentOS — Interactive Demo")
547 print("=" * 60)
548 print()
549 print(" 选择演示场景:")
550 print(" [1] 天气助手(默认)")
551 print(" [2] 文件操作")
552 print(" [3] 健康自检")
553 print()
555 try:
556 choice = input(" 请输入数字 (1-3) [1]: ").strip() or "1"
557 except (EOFError, KeyboardInterrupt):
558 choice = "1"
560 if choice == "3":
561 _run_hello()
562 elif choice == "2":
563 _run_file_demo(verbose)
564 else:
565 _run_demo(verbose)
566 return
568 # Run task
569 task_start = 1 if cmd == "run" else 0
570 task = " ".join(args[task_start:])
571 if not task.strip():
572 no_task_provided()
574 agent = _build_agent(verbose=verbose)
575 # 自动记录 tracker
576 from agentos.dashboard.tracker import Tracker
577 import uuid, time
578 tracker = Tracker.get()
579 session_id = f"run-{uuid.uuid4().hex[:12]}"
580 rec = tracker.start_session(session_id, task, model="auto", provider="auto")
581 t0 = time.time()
582 try:
583 result = agent.run(task)
584 elapsed = (time.time() - t0) * 1000
585 tracker.finish_session(
586 session_id,
587 status="completed" if result.success else "error",
588 error=result.error if not result.success else "",
589 total_cost=result.total_cost_usd if hasattr(result, 'total_cost_usd') else 0.0,
590 )
591 print(f"\n{'─' * 60}")
592 if result.success:
593 print(f"Result ({(result.total_duration_ms/1000):.1f}s, "
594 f"{result.total_steps} steps, "
595 f"{result.total_tokens} tokens, "
596 f"${result.total_cost_usd:.4f}):")
597 print(f"{result.final_answer}")
598 else:
599 print(f"Error: {result.error}")
600 except Exception as e:
601 tracker.finish_session(session_id, status="error", error=str(e))
602 raise
605def _run_marketplace(args: list[str]):
606 """Skill Marketplace CLI dispatcher."""
608 if not args or args[0] in ("help", "--help", "-h"):
609 print("AgentOS Skill Marketplace\n")
610 print("Subcommands:")
611 print(" search <query> 搜索技能市场")
612 print(" install <name|path|url> 安装技能(PyPI / 本地 / GitHub)")
613 print(" list 列出已安装技能")
614 print(" info <name> 查看技能详情")
615 print(" update <name> 更新技能到最新版")
616 print(" uninstall <name> 卸载技能")
617 print(" stats 市场统计")
618 print("\n兼容格式: agentos / openclaw / mcp / generic")
619 return
621 from agentos.marketplace import SkillRegistry, InstallResult
623 registry = SkillRegistry()
624 sub = args[0]
625 rest = args[1:]
627 if sub == "search":
628 query = " ".join(rest) if rest else ""
629 print(f"Searching marketplace for '{query or 'all'}'...\n")
630 results = registry.search(query)
631 if not results:
632 print("No skills found. Try a broader query, or publish your own with 'agentos-skill-<name>' on PyPI.")
633 return
634 print(f"{'Name':<24s} {'Version':<12s} {'Source':<10s} Description")
635 print("-" * 80)
636 for r in results:
637 desc = r.description[:60] if r.description else "-"
638 print(f"{r.name:<24s} {r.version:<12s} {r.source:<10s} {desc}")
640 elif sub == "install":
641 if not rest:
642 print("Usage: agentos marketplace install <name|path|url>")
643 return
644 target = " ".join(rest)
645 print(f"Installing '{target}'...")
646 result = registry.install(target)
647 if result.success and result.manifest:
648 m = result.manifest
649 print(f"\n Installed: {m.name} v{m.version} [{m.format.value}]")
650 print(f" Source: {result.install_type}")
651 if m.description:
652 print(f" Description: {m.description}")
653 if result.dep_installed:
654 print(f" Dependencies: {', '.join(result.dep_installed)}")
655 if m.tools:
656 print(f" Tools: {', '.join(t.name for t in m.tools)}")
657 else:
658 print(f" Failed: {result.error}")
660 elif sub == "list":
661 skills = registry.list_installed()
662 if not skills:
663 print("No skills installed. Try 'agentos marketplace install <name>'.")
664 return
665 print(f"{'Name':<24s} {'Version':<12s} {'Format':<12s} {'Source':<10s} Description")
666 print("-" * 90)
667 for m in skills:
668 desc = (m.description or "")[:50]
669 print(f"{m.name:<24s} {m.version:<12s} {m.format.value:<12s} {m.source:<10s} {desc}")
671 elif sub == "info":
672 if not rest:
673 print("Usage: agentos marketplace info <name>")
674 return
675 name = rest[0]
676 m = registry.get_installed(name)
677 if not m:
678 print(f"Skill '{name}' not installed. Try 'agentos marketplace search {name}'.")
679 return
680 print(f"\n {m.name} v{m.version} [{m.format.value}]")
681 print(f" {'─' * 40}")
682 print(f" 描述: {m.description or '-'}")
683 print(f" 作者: {m.author}")
684 print(f" 许可: {m.license_}")
685 print(f" 格式: {m.format.value}")
686 print(f" 来源: {m.source}")
687 print(f" 安装路径: {m.install_path or '-'}")
688 if m.entrypoint:
689 print(f" 入口: {m.entrypoint}")
690 if m.repository:
691 print(f" 仓库: {m.repository}")
692 if m.homepage:
693 print(f" 主页: {m.homepage}")
694 if m.tags:
695 print(f" 标签: {', '.join(m.tags)}")
696 if m.tools:
697 print(f" 工具 ({len(m.tools)}):")
698 for t in m.tools:
699 print(f" - {t.name}: {t.description or 'N/A'}")
700 if m.dependencies:
701 print(f" 依赖: {', '.join(m.dependencies)}")
702 if m.mcp_command:
703 print(f" MCP: {m.mcp_command} {' '.join(m.mcp_args)}")
704 print(f" MCP 类型: {m.mcp_type}")
705 if m.min_agentos_version:
706 print(f" 要求版本: >= {m.min_agentos_version}")
707 print()
709 elif sub == "update":
710 if not rest:
711 print("Usage: agentos marketplace update <name>")
712 return
713 name = rest[0]
714 print(f"Updating '{name}'...")
715 result = registry.update(name)
716 if result.success and result.manifest:
717 print(f" Updated: {result.manifest.name} v{result.manifest.version}")
718 else:
719 print(f" Failed: {result.error}")
721 elif sub == "uninstall":
722 if not rest:
723 print("Usage: agentos marketplace uninstall <name>")
724 return
725 name = rest[0]
726 if registry.uninstall(name):
727 print(f"Uninstalled: {name}")
728 else:
729 print(f"Skill '{name}' not installed.")
731 elif sub == "stats":
732 stats = registry.stats()
733 print(f"Marketplace Stats:")
734 print(f" Total installed: {stats['total']}")
735 print(f" Market dir: {stats['market_dir']}")
736 if stats.get("by_format"):
737 print(f" By format:")
738 for fmt, count in stats["by_format"].items():
739 print(f" {fmt}: {count}")
741 else:
742 print(f"Unknown marketplace command: {sub}")
743 print("Try: agentos marketplace --help")
746def _run_enterprise(args: list[str]):
747 """Enterprise CLI dispatcher."""
749 if not args or args[0] in ("help", "--help", "-h"):
750 print("AgentOS Enterprise CLI\n")
751 print("Subcommands:")
752 print(" api-key create|list|revoke|stats API Key management")
753 print(" tenant create|list|stats Multi-tenant management")
754 print(" audit stats|recent|export Audit logging")
755 return
757 sub = args[0]
758 from agentos.enterprise import (
759 APIKeyManager, TenantManager, AuditLogger,
760 KeyCreateRequest, KeyScope, TenantTier,
761 )
763 if sub == "api-key":
764 _run_enterprise_api_key(args[1:], APIKeyManager())
765 elif sub == "tenant":
766 _run_enterprise_tenant(args[1:], TenantManager())
767 elif sub == "audit":
768 _run_enterprise_audit(args[1:], AuditLogger())
769 else:
770 print(f"Unknown enterprise subcommand: {sub}")
771 print("Try: agentos enterprise --help")
774def _run_enterprise_api_key(args: list[str], mgr):
775 if not args:
776 print("Usage: agentos enterprise api-key <create|list|revoke|stats>")
777 return
778 cmd = args[0]
779 if cmd == "create":
780 name = args[1] if len(args) > 1 else "cli-key"
781 result = mgr.create_key(KeyCreateRequest(name=name, scopes=[KeyScope.AGENT_RUN, KeyScope.READ]))
782 print(f"Key created: {result.key_id}")
783 print(f"Plaintext (only shown once): {result.plaintext_key}")
784 print(f"Prefix: {result.key_prefix}")
785 print(f"Scopes: {[s.value for s in result.scopes]}")
786 elif cmd == "list":
787 keys = mgr.list_keys()
788 if not keys:
789 print("No API keys.")
790 return
791 print(f"{'ID':<28s} {'Name':<24s} {'Status':<10s} {'Usage':<8s}")
792 print("-" * 72)
793 for k in keys:
794 status = "revoked" if k.revoked else "active"
795 print(f"{k.key_id:<28s} {k.name:<24s} {status:<10s} {k.usage_count:<8d}")
796 elif cmd == "revoke":
797 if len(args) < 2:
798 print("Usage: agentos enterprise api-key revoke <key_id>")
799 return
800 ok = mgr.revoke_key(args[1])
801 print(f"{'Revoked' if ok else 'Not found or already revoked'}: {args[1]}")
802 elif cmd == "stats":
803 stats = mgr.stats()
804 print(f"Total: {stats['total']} Active: {stats['active']} Revoked: {stats['revoked']} "
805 f"Total usage: {stats['total_usage_count']}")
806 else:
807 print(f"Unknown api-key command: {cmd}")
810def _run_enterprise_tenant(args: list[str], mgr):
811 if not args:
812 print("Usage: agentos enterprise tenant <create|list|stats>")
813 return
814 cmd = args[0]
815 if cmd == "create":
816 name = args[1] if len(args) > 1 else "default"
817 tier_str = args[2] if len(args) > 2 else "free"
818 tier = TenantTier(tier_str) if tier_str in [t.value for t in TenantTier] else TenantTier.FREE
819 tenant = mgr.create_tenant(name=name, tier=tier)
820 print(f"Tenant created: {tenant.tenant_id}")
821 print(f"Name: {tenant.name} Tier: {tenant.tier.value}")
822 elif cmd == "list":
823 tenants = mgr.list_tenants()
824 if not tenants:
825 print("No tenants.")
826 return
827 print(f"{'ID':<20s} {'Name':<20s} {'Tier':<12s} {'Status':<10s}")
828 print("-" * 64)
829 for t in tenants:
830 print(f"{t.tenant_id:<20s} {t.name:<20s} {t.tier.value:<12s} {t.status.value:<10s}")
831 elif cmd == "stats":
832 stats = mgr.stats()
833 print(f"Total tenants: {stats['total']}")
834 print(f"By tier: {stats['by_tier']}")
835 print(f"By status: {stats['by_status']}")
836 else:
837 print(f"Unknown tenant command: {cmd}")
840def _run_enterprise_audit(args: list[str], logger):
841 if not args:
842 print("Usage: agentos enterprise audit <stats|recent|export>")
843 return
844 cmd = args[0]
845 if cmd == "stats":
846 stats = logger.stats()
847 print(f"Total events: {stats['total_events']}")
848 print(f"By category: {stats['by_category']}")
849 print(f"By severity: {stats['by_severity']}")
850 elif cmd == "recent":
851 n = int(args[1]) if len(args) > 1 else 10
852 events = logger.get_recent(n)
853 if not events:
854 print("No audit events.")
855 return
856 for e in events:
857 ts = __import__('time').strftime("%H:%M:%S", __import__('time').gmtime(e.timestamp))
858 print(f"[{ts}] {e.category.value:8s} {e.action:24s} {e.status}")
859 elif cmd == "export":
860 fmt = args[1] if len(args) > 1 else "json"
861 if fmt == "csv":
862 print(logger.export_csv()[:2000])
863 else:
864 print(logger.export_json()[:2000])
865 print("... (truncated to 2000 chars)")
866 else:
867 print(f"Unknown audit command: {cmd}")
870def _run_demo(verbose: bool):
871 from agentos.llm import create_provider, Tool, ToolParameter
872 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig
874 def get_weather(city: str) -> str:
875 data = {
876 "北京": "北京:晴,22°C,湿度 35%,东北风 3 级",
877 "上海": "上海:多云转阴,28°C,湿度 70%,东南风 2 级",
878 "深圳": "深圳:雷阵雨,31°C,湿度 85%",
879 }
880 return data.get(city, f"{city}: 数据暂缺")
882 def get_stock(symbol: str) -> str:
883 prices = {
884 "AAPL": "$220.50 (+1.2%)",
885 "TSLA": "$248.30 (-0.8%)",
886 }
887 return prices.get(symbol.upper(), f"{symbol}: 未找到")
889 executor = ToolExecutor()
890 executor.register(
891 Tool.from_function("get_weather", "获取城市天气", {
892 "city": ToolParameter(type="string", description="城市名"),
893 }),
894 get_weather,
895 )
896 executor.register(
897 Tool.from_function("get_stock_price", "获取股票价格", {
898 "symbol": ToolParameter(type="string", description="股票代码,如 AAPL"),
899 }),
900 get_stock,
901 )
903 if os.getenv("OPENAI_API_KEY"):
904 provider = create_provider("openai")
905 elif os.getenv("DEEPSEEK_API_KEY"):
906 provider = create_provider("deepseek")
907 else:
908 print("\n ⚠️ Mock 演示 — 配置 API Key 获取真实 AI 响应\n")
909 print(" 运行: agentos init\n")
910 provider = MockLLMProvider([
911 MockLLMProvider.tool_response(
912 "get_weather", {"city": "北京"}, tool_call_id="tc_w1",
913 ),
914 MockLLMProvider.text_response(
915 "北京目前天气晴,气温 22°C,湿度 35%,东北风 3 级。"
916 "适合户外活动,建议带薄外套。"
917 ),
918 ])
920 agent = ToolAgent(
921 provider=provider,
922 tool_executor=executor,
923 config=AgentConfig(verbose=verbose, temperature=0.0),
924 system_prompt="你是一个天气助手。用工具获取天气/股票信息后直接回答。",
925 )
927 result = agent.run("北京天气怎么样?")
928 print(f"\nTask: 北京天气怎么样?")
929 print(f"Steps: {result.total_steps} | Time: {(result.total_duration_ms/1000):.1f}s")
930 print(f"Tokens: {result.total_tokens} | Cost: ${result.total_cost_usd:.4f}")
931 print(f"Answer: {result.final_answer}")
934if __name__ == "__main__":
935 main()