Coverage for agentos/cli/main.py: 4%
653 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 19:15 +0800
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-06 19:15 +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.agent.tool_agent import AgentConfig, MockLLMProvider, ToolAgent, ToolExecutor
11from agentos.cli.errors import (
12 no_provider_configured,
13 no_task_provided,
14 welcome,
15)
16from agentos.llm.base import Tool, ToolParameter
17from agentos.llm.factory import create_provider
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
92 try:
93 with tempfile.TemporaryDirectory() as td:
94 result = subprocess.run(
95 command,
96 shell=True,
97 capture_output=True,
98 text=True,
99 timeout=30,
100 cwd=td,
101 )
102 out = result.stdout.strip()
103 err = result.stderr.strip()
104 if err:
105 return f"stdout:\n{out}\n\nstderr:\n{err}"
106 return out or "(no output)"
107 except subprocess.TimeoutExpired:
108 return "Error: command timed out (30s)"
109 except Exception as e:
110 return f"Error: {e}"
113def _read_file(file_path: str) -> str:
114 try:
115 with open(file_path) as f:
116 return f.read()
117 except Exception as e:
118 return f"Error: {e}"
121def _list_directory(path: str) -> str:
122 try:
123 entries = os.listdir(path)
124 return "\n".join(sorted(entries)) or "(empty)"
125 except Exception as e:
126 return f"Error: {e}"
129def _write_file(file_path: str, content: str) -> str:
130 try:
131 os.makedirs(os.path.dirname(file_path) or ".", exist_ok=True)
132 with open(file_path, "w") as f:
133 f.write(content)
134 return f"Written {len(content)} bytes to {file_path}"
135 except Exception as e:
136 return f"Error: {e}"
139def _build_agent(provider_name: str = "", verbose: bool = False) -> ToolAgent:
140 if provider_name:
141 provider = create_provider(provider_name)
142 elif os.getenv("OPENAI_API_KEY"):
143 provider = create_provider("openai")
144 provider_name = "openai"
145 elif os.getenv("DEEPSEEK_API_KEY"):
146 provider = create_provider("deepseek")
147 provider_name = "deepseek"
148 elif os.getenv("ANTHROPIC_API_KEY"):
149 provider = create_provider("anthropic")
150 provider_name = "anthropic"
151 else:
152 welcome()
153 no_provider_configured()
155 config = AgentConfig(verbose=verbose)
156 executor = _build_executor()
158 return ToolAgent(
159 provider=provider,
160 tool_executor=executor,
161 config=config,
162 system_prompt=(
163 "你是 AgentOS 智能助手。你可以使用以下工具完成任务:\n"
164 "- run_shell: 执行 Shell 命令\n"
165 "- read_file: 读取文件内容\n"
166 "- write_file: 写入文本到文件\n"
167 "- list_directory: 列出目录内容\n\n"
168 "当需要操作文件或执行命令时调用对应工具。"
169 "获得足够信息后直接回答,不要再调用工具。"
170 "使用中文回复。"
171 ),
172 )
175def _run_hello():
176 """一键 hello world 体验 — 无需配置,始终可用。"""
177 import time
179 from agentos import __version__
181 print(f" \033[36mNexus AgentOS\033[0m v{__version__}")
182 print(" \033[2m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
183 print()
185 # Step 1: check provider
186 provider_label = "Mock(演示模式)"
187 provider_color = "\033[33m"
188 if os.environ.get("OPENAI_API_KEY"):
189 provider_label = "OpenAI (gpt-4o-mini)"
190 provider_color = "\033[32m"
191 elif os.environ.get("DEEPSEEK_API_KEY"):
192 provider_label = "DeepSeek (deepseek-chat)"
193 provider_color = "\033[32m"
194 elif os.environ.get("ANTHROPIC_API_KEY"):
195 provider_label = "Anthropic (claude-sonnet-4)"
196 provider_color = "\033[32m"
198 steps = [
199 ("检测环境", f"{provider_color}{provider_label}\033[0m"),
200 ("核心引擎", "\033[32mToolAgent 多步推理循环\033[0m"),
201 ("工具系统", "\033[32mShell / 文件读写 / 目录浏览\033[0m"),
202 ("安全护栏", "\033[32mGuardrails PII注入检测\033[0m"),
203 ("可观测性", "\033[32mTracker + Dashboard SSE\033[0m"),
204 ("MCP 协议", "\033[32mstdio JSON-RPC 2.0\033[0m"),
205 ("RAG 管道", "\033[32mChroma/FAISS 向量检索\033[0m"),
206 ("企业特性", "\033[32mRBAC/多租户/审计/API Key\033[0m"),
207 ]
209 for label, status in steps:
210 time.sleep(0.12)
211 print(f" \033[2m▸\033[0m {label:<12s} {status}")
213 time.sleep(0.3)
214 print()
215 print(" \033[1m一切就绪。\033[0m")
216 print()
218 if (
219 os.environ.get("OPENAI_API_KEY")
220 or os.environ.get("DEEPSEEK_API_KEY")
221 or os.environ.get("ANTHROPIC_API_KEY")
222 ):
223 print(" 快速开始:")
224 print(' \033[32magentos\033[0m "用一句话解释什么是递归"')
225 print(" \033[32magentos demo\033[0m")
226 else:
227 print(" 下一步(30 秒配置):")
228 print(" \033[32magentos init\033[0m 终端交互式配置")
229 print(" \033[32magentos config-panel\033[0m 浏览器图形界面")
230 print()
233def _run_file_demo(verbose: bool):
234 """文件操作演示 — 创建、读取、列表。"""
235 from agentos.agent.tool_agent import AgentConfig, ToolAgent, ToolExecutor
237 executor = ToolExecutor()
239 def write_file(path: str, content: str) -> str:
240 import os as _os
242 try:
243 _os.makedirs(_os.path.dirname(path) or ".", exist_ok=True)
244 with open(path, "w") as f:
245 f.write(content)
246 return f"已写入 {path} ({len(content)} 字节)"
247 except Exception as e:
248 return f"写入失败: {e}"
250 def read_file(path: str) -> str:
251 try:
252 with open(path) as f:
253 return f.read()
254 except Exception as e:
255 return f"读取失败: {e}"
257 def list_dir(path: str) -> str:
258 import os as _os
260 try:
261 return "\n".join(sorted(_os.listdir(path))) or "(空)"
262 except Exception as e:
263 return f"列出失败: {e}"
265 executor.register(
266 Tool.from_function(
267 "write_file",
268 "写入文本到文件",
269 {
270 "path": ToolParameter(type="string", description="文件路径"),
271 "content": ToolParameter(type="string", description="文件内容"),
272 },
273 ),
274 write_file,
275 )
276 executor.register(
277 Tool.from_function(
278 "read_file",
279 "读取文件内容",
280 {
281 "path": ToolParameter(type="string", description="文件路径"),
282 },
283 ),
284 read_file,
285 )
286 executor.register(
287 Tool.from_function(
288 "list_dir",
289 "列出目录内容",
290 {
291 "path": ToolParameter(type="string", description="目录路径"),
292 },
293 ),
294 list_dir,
295 )
297 if os.getenv("OPENAI_API_KEY"):
298 provider = create_provider("openai")
299 elif os.getenv("DEEPSEEK_API_KEY"):
300 provider = create_provider("deepseek")
301 else:
302 print("\n 文件操作演示需要 API Key(Mock 不支持动态文件操作)。")
303 print(" 运行 agentos init 配置后重试。\n")
304 return
306 agent = ToolAgent(
307 provider=provider,
308 tool_executor=executor,
309 config=AgentConfig(verbose=verbose, temperature=0.0),
310 system_prompt="你是文件操作助手。用工具完成任务后用中文汇报结果。",
311 )
313 demo_dir = "/tmp/agentos_file_demo"
314 print(f"\n 演示目录: {demo_dir}")
315 print()
317 result = agent.run(
318 f"在 {demo_dir} 下创建 hello.txt 写入 'Hello from AgentOS!',列出目录内容,再读取 hello.txt 确认。"
319 )
320 print(f"\n 耗时: {result.total_duration_ms/1000:.1f}s | 步数: {result.total_steps}")
321 print(f" Token: {result.total_tokens} | 费用: ${result.total_cost_usd:.4f}")
322 print(f" 结果: {result.final_answer}")
325def _run_production_agent(cli_args: list[str], verbose: bool = False) -> None:
326 """Run a task through ProductionAgent (ModelRouter + AuditLogger + SmartCache)."""
327 host = "0.0.0.0"
328 port = 8090
330 i = 0
331 while i < len(cli_args):
332 a = cli_args[i]
333 if a == "--host" and i + 1 < len(cli_args):
334 host = cli_args[i + 1]
335 i += 2
336 continue
337 elif a == "--port" and i + 1 < len(cli_args):
338 port = int(cli_args[i + 1])
339 i += 2
340 continue
341 elif a == "--budget" and i + 1 < len(cli_args):
342 i += 2
343 continue
344 else:
345 i += 1
347 try:
348 from agentos.agent.model_router import ModelRouter
349 from agentos.agent.production import ProductionConfig
350 from agentos.llm.factory import create_provider
351 from agentos.llm.smart_cache import CacheConfig, SmartCache
352 from agentos.server.agent_api import create_agent_api
353 except ImportError as e:
354 print(f"Error: missing dependency — {e}")
355 sys.exit(1)
357 try:
358 provider = create_provider("openai")
359 except Exception:
360 print("Warning: no live provider, using mock.")
361 from agentos.agent.tool_agent import MockLLMProvider
363 provider = MockLLMProvider()
365 executor = _build_executor()
366 router = ModelRouter.with_defaults(daily_budget_usd=50.0)
367 cache = SmartCache(CacheConfig(enabled=True, fuzzy_enabled=True))
369 app = create_agent_api(
370 provider=provider,
371 executor=executor,
372 router=router,
373 cache=cache,
374 config=ProductionConfig(budget_usd=50.0),
375 )
377 import uvicorn
379 print(f"AgentOS Production API → http://{host}:{port}")
380 print(" POST /agent/run GET /agent/health GET /agent/stats")
381 uvicorn.run(app, host=host, port=port, log_level="info" if verbose else "warning")
384def main():
385 args = sys.argv[1:]
386 verbose = "--verbose" in args or "-v" in args
387 args = [a for a in args if a not in ("--verbose", "-v")]
389 if not args or args[0] in ("help", "--help", "-h"):
390 from agentos import __version__
391 from agentos.cli.init import config_status_text
393 print(f"AgentOS v{__version__} — Production Agent Framework CLI\n")
394 print(f"Provider: {config_status_text()}\n")
395 print("Usage:")
396 print(" agentos init Interactive setup wizard (recommended)")
397 print(" agentos hello One-step health check & quick intro")
398 print(" agentos config-panel Open web config panel in browser")
399 print(" agentos <task> Run a task with the autonomous agent")
400 print(" agentos run <task> Same as above")
401 print(" agentos demo Run interactive demo (weather/stock/files)")
402 print(" agentos serve Start API server (port 8080)")
403 print(" agentos run [--port PORT] Start ProductionAgent API (port 8090)")
404 print(" agentos daemon <cmd> Server daemon: start|stop|status|restart|run")
405 print(" agentos version Show version")
406 print(" agentos skills List agent marketplace skills")
407 print(" agentos docs [output-dir] Generate API reference docs (→ docs/api/)")
408 print(" agentos dashboard Open web trace dashboard (port 18500)")
409 print(" agentos mcp-server Start MCP server (stdio) for Claude Desktop etc.")
410 print(" agentos desktop Launch web desktop client (port 19999)")
411 print(" agentos desktop-shell Launch native desktop shell (pywebview)")
412 print(" agentos enterprise <cmd> Enterprise features: api-key, tenant, audit")
413 print(
414 " agentos marketplace <cmd> Skill marketplace: search|install|list|update|uninstall|stats"
415 )
416 print(
417 " agentos rollback <version> Rollback to a previous version (--list|--verify|--prune)"
418 )
419 print("\nOptions:")
420 print(" -v, --verbose Show agent step details")
421 print(" --provider <name> Force provider: openai|deepseek|anthropic")
422 print("\nProvider (auto-detect via env vars):")
423 print(" OPENAI_API_KEY → OpenAI (gpt-4o-mini)")
424 print(" DEEPSEEK_API_KEY → DeepSeek (deepseek-chat)")
425 print(" ANTHROPIC_API_KEY → Anthropic (claude-sonnet-4)")
426 print(" (none set) → Mock demo mode — run 'agentos init' to configure")
427 print("\nExamples:")
428 print(" agentos hello # 30s quick tour")
429 print(" agentos init # 1-minute setup wizard")
430 print(' agentos "列出当前目录的文件"')
431 print(' agentos "创建一个 hello.py 打印 Hello World"')
432 print(" agentos demo")
433 sys.exit(0)
435 cmd = args[0]
436 if cmd == "init":
437 from agentos.cli.init import init_cli
439 init_cli(args)
440 return
442 if cmd == "config-panel":
443 from agentos.cli.config_panel import start_panel
445 start_panel()
446 return
448 if cmd == "status":
449 from agentos.cli.init import _detect_current_config, config_status_text
451 print(f"Provider: {config_status_text()}\n")
452 config = _detect_current_config()
453 for name, info in config["providers"].items():
454 from agentos.cli.init import PROVIDERS
456 p = PROVIDERS[name]
457 status_icon = "✅" if info["env_set"] else "⬜"
458 key_info = info.get("key_preview", "未配置") or "未配置"
459 in_config = " (配置文件)" if info.get("in_config") else ""
460 print(f" {status_icon} {p['label']:20s} {key_info:25s}{in_config}")
461 return
463 if cmd == "version":
464 from agentos import __version__
466 print(f"AgentOS v{__version__}")
467 return
469 if cmd == "skills":
470 from agentos.agents.market import AgentMarket
472 market = AgentMarket()
473 stats = market.stats()
474 print(f"Agent Skill Market: {stats['total']} skills\n")
475 for cat, count in stats["by_category"].items():
476 skills = market.list_by_category(cat)
477 print(f" [{cat}] ({count})")
478 for s in skills:
479 print(f" {s.name}: {s.description}")
480 return
482 if cmd == "docs":
483 import os
485 from agentos.docs.generator import generate_api_docs, generate_quickstart
487 if not os.path.isdir("agentos"):
488 print("Error: run 'agentos docs' from the agentos project root directory")
489 sys.exit(1)
490 output_dir = args[1] if len(args) > 1 else "docs"
491 os.makedirs(output_dir, exist_ok=True)
492 api_path = os.path.join(output_dir, "api_reference.md")
493 qs_path = os.path.join(output_dir, "quickstart.md")
494 md = generate_api_docs("agentos", api_path)
495 generate_quickstart(qs_path)
496 module_count = len(
497 [d for d in os.listdir("agentos") if os.path.isdir(os.path.join("agentos", d))]
498 )
499 print(f"Generated API docs: {api_path} ({len(md.splitlines())} lines)")
500 print(f"Generated Quickstart: {qs_path}")
501 print(f"Scanned ~{module_count} source modules")
502 return
504 if cmd == "dashboard":
505 from agentos.dashboard.server import start_dashboard
507 print("Starting AgentOS Dashboard...")
508 start_dashboard()
509 return
511 if cmd == "mcp-server":
512 from agentos.mcp.server import start_mcp_server
514 port = 0
515 for i, a in enumerate(args[1:]):
516 if a == "--port" and i + 2 < len(args):
517 port = int(args[i + 2])
518 start_mcp_server(port=port)
519 return
521 if cmd == "desktop":
522 from agentos.desktop.server import launch_desktop
524 host = "0.0.0.0"
525 port = 19999
526 mode = "dev"
527 for i, a in enumerate(args[1:]):
528 if a == "--host" and i + 2 < len(args):
529 host = args[i + 2]
530 elif a == "--port" and i + 2 < len(args):
531 port = int(args[i + 2])
532 elif a == "--safe":
533 mode = "safe"
534 launch_desktop(host=host, port=port, mode=mode)
535 return
537 if cmd == "desktop-shell":
538 from agentos.desktop.shell import main as shell_main
540 sys.argv = [sys.argv[0]] + args[1:]
541 shell_main()
542 return
544 if cmd == "enterprise":
545 _run_enterprise(args[1:])
546 return
548 if cmd == "marketplace":
549 _run_marketplace(args[1:])
550 return
552 if cmd == "rollback":
553 from agentos.cli.rollback import rollback_cli
555 sys.exit(rollback_cli(args[1:]))
557 if cmd == "run":
558 _run_production_agent(args[1:], verbose)
559 return
561 if cmd == "serve":
562 host = "0.0.0.0"
563 port = 8080
564 for i, a in enumerate(args[1:]):
565 if a == "--host" and i + 2 < len(args):
566 host = args[i + 2]
567 elif a == "--port" and i + 2 < len(args):
568 port = int(args[i + 2])
569 from agentos.api.server import AgentAPI
570 from agentos.core.context import ContextManager
571 from agentos.core.loop import AgentLoop
572 from agentos.models.router import RECOMMENDED_CONFIG, ModelRouter
573 from agentos.tools.code_agent import CodeAgentTool, ShellTool
574 from agentos.tools.file_tools import ListDirectoryTool, ReadFileTool, WriteFileTool
575 from agentos.tools.registry import ToolRegistry
576 from agentos.tools.web_tools import WebFetchTool
578 registry = ToolRegistry()
579 registry.register_many(
580 [
581 ReadFileTool(),
582 WriteFileTool(),
583 ListDirectoryTool(),
584 CodeAgentTool(),
585 ShellTool(),
586 WebFetchTool(),
587 ]
588 )
589 ctx = ContextManager(system_prompt="AgentOS API Server v1.0")
590 router = ModelRouter(RECOMMENDED_CONFIG)
591 loop = AgentLoop(model_router=router, tool_registry=registry, context_manager=ctx)
592 api = AgentAPI(loop)
593 print(f"AgentOS API starting on http://{host}:{port}")
594 api.serve(host=host, port=port)
595 return
597 if cmd == "daemon":
598 from agentos.server.daemon import daemon_main
600 sys.exit(daemon_main(args[1:]))
602 if cmd == "hello":
603 _run_hello()
604 return
606 if cmd == "demo":
607 print("=" * 60)
608 print(" AgentOS — Interactive Demo")
609 print("=" * 60)
610 print()
611 print(" 选择演示场景:")
612 print(" [1] 天气助手(默认)")
613 print(" [2] 文件操作")
614 print(" [3] 健康自检")
615 print()
617 try:
618 choice = input(" 请输入数字 (1-3) [1]: ").strip() or "1"
619 except (EOFError, KeyboardInterrupt):
620 choice = "1"
622 if choice == "3":
623 _run_hello()
624 elif choice == "2":
625 _run_file_demo(verbose)
626 else:
627 _run_demo(verbose)
628 return
630 # Run task
631 task_start = 1 if cmd == "run" else 0
632 task = " ".join(args[task_start:])
633 if not task.strip():
634 no_task_provided()
636 agent = _build_agent(verbose=verbose)
637 # 自动记录 tracker
638 import time
639 import uuid
641 from agentos.dashboard.tracker import Tracker
643 tracker = Tracker.get()
644 session_id = f"run-{uuid.uuid4().hex[:12]}"
645 tracker.start_session(session_id, task, model="auto", provider="auto")
646 t0 = time.time()
647 try:
648 result = agent.run(task)
649 (time.time() - t0) * 1000
650 tracker.finish_session(
651 session_id,
652 status="completed" if result.success else "error",
653 error=result.error if not result.success else "",
654 total_cost=result.total_cost_usd if hasattr(result, "total_cost_usd") else 0.0,
655 )
656 print(f"\n{'─' * 60}")
657 if result.success:
658 print(
659 f"Result ({(result.total_duration_ms/1000):.1f}s, "
660 f"{result.total_steps} steps, "
661 f"{result.total_tokens} tokens, "
662 f"${result.total_cost_usd:.4f}):"
663 )
664 print(f"{result.final_answer}")
665 else:
666 print(f"Error: {result.error}")
667 except Exception as e:
668 tracker.finish_session(session_id, status="error", error=str(e))
669 raise
672def _run_marketplace(args: list[str]):
673 """Skill Marketplace CLI dispatcher."""
675 if not args or args[0] in ("help", "--help", "-h"):
676 print("AgentOS Skill Marketplace\n")
677 print("Subcommands:")
678 print(" search <query> 搜索技能市场")
679 print(" install <name|path|url> 安装技能(PyPI / 本地 / GitHub)")
680 print(" list 列出已安装技能")
681 print(" info <name> 查看技能详情")
682 print(" update <name> 更新技能到最新版")
683 print(" uninstall <name> 卸载技能")
684 print(" stats 市场统计")
685 print("\n兼容格式: agentos / openclaw / mcp / generic")
686 return
688 from agentos.marketplace import SkillRegistry
690 registry = SkillRegistry()
691 sub = args[0]
692 rest = args[1:]
694 if sub == "search":
695 query = " ".join(rest) if rest else ""
696 print(f"Searching marketplace for '{query or 'all'}'...\n")
697 results = registry.search(query)
698 if not results:
699 print(
700 "No skills found. Try a broader query, or publish your own with 'agentos-skill-<name>' on PyPI."
701 )
702 return
703 print(f"{'Name':<24s} {'Version':<12s} {'Source':<10s} Description")
704 print("-" * 80)
705 for r in results:
706 desc = r.description[:60] if r.description else "-"
707 print(f"{r.name:<24s} {r.version:<12s} {r.source:<10s} {desc}")
709 elif sub == "install":
710 if not rest:
711 print("Usage: agentos marketplace install <name|path|url>")
712 return
713 target = " ".join(rest)
714 print(f"Installing '{target}'...")
715 result = registry.install(target)
716 if result.success and result.manifest:
717 m = result.manifest
718 print(f"\n Installed: {m.name} v{m.version} [{m.format.value}]")
719 print(f" Source: {result.install_type}")
720 if m.description:
721 print(f" Description: {m.description}")
722 if result.dep_installed:
723 print(f" Dependencies: {', '.join(result.dep_installed)}")
724 if m.tools:
725 print(f" Tools: {', '.join(t.name for t in m.tools)}")
726 else:
727 print(f" Failed: {result.error}")
729 elif sub == "list":
730 skills = registry.list_installed()
731 if not skills:
732 print("No skills installed. Try 'agentos marketplace install <name>'.")
733 return
734 print(f"{'Name':<24s} {'Version':<12s} {'Format':<12s} {'Source':<10s} Description")
735 print("-" * 90)
736 for m in skills:
737 desc = (m.description or "")[:50]
738 print(f"{m.name:<24s} {m.version:<12s} {m.format.value:<12s} {m.source:<10s} {desc}")
740 elif sub == "info":
741 if not rest:
742 print("Usage: agentos marketplace info <name>")
743 return
744 name = rest[0]
745 m = registry.get_installed(name)
746 if not m:
747 print(f"Skill '{name}' not installed. Try 'agentos marketplace search {name}'.")
748 return
749 print(f"\n {m.name} v{m.version} [{m.format.value}]")
750 print(f" {'─' * 40}")
751 print(f" 描述: {m.description or '-'}")
752 print(f" 作者: {m.author}")
753 print(f" 许可: {m.license_}")
754 print(f" 格式: {m.format.value}")
755 print(f" 来源: {m.source}")
756 print(f" 安装路径: {m.install_path or '-'}")
757 if m.entrypoint:
758 print(f" 入口: {m.entrypoint}")
759 if m.repository:
760 print(f" 仓库: {m.repository}")
761 if m.homepage:
762 print(f" 主页: {m.homepage}")
763 if m.tags:
764 print(f" 标签: {', '.join(m.tags)}")
765 if m.tools:
766 print(f" 工具 ({len(m.tools)}):")
767 for t in m.tools:
768 print(f" - {t.name}: {t.description or 'N/A'}")
769 if m.dependencies:
770 print(f" 依赖: {', '.join(m.dependencies)}")
771 if m.mcp_command:
772 print(f" MCP: {m.mcp_command} {' '.join(m.mcp_args)}")
773 print(f" MCP 类型: {m.mcp_type}")
774 if m.min_agentos_version:
775 print(f" 要求版本: >= {m.min_agentos_version}")
776 print()
778 elif sub == "update":
779 if not rest:
780 print("Usage: agentos marketplace update <name>")
781 return
782 name = rest[0]
783 print(f"Updating '{name}'...")
784 result = registry.update(name)
785 if result.success and result.manifest:
786 print(f" Updated: {result.manifest.name} v{result.manifest.version}")
787 else:
788 print(f" Failed: {result.error}")
790 elif sub == "uninstall":
791 if not rest:
792 print("Usage: agentos marketplace uninstall <name>")
793 return
794 name = rest[0]
795 if registry.uninstall(name):
796 print(f"Uninstalled: {name}")
797 else:
798 print(f"Skill '{name}' not installed.")
800 elif sub == "stats":
801 stats = registry.stats()
802 print("Marketplace Stats:")
803 print(f" Total installed: {stats['total']}")
804 print(f" Market dir: {stats['market_dir']}")
805 if stats.get("by_format"):
806 print(" By format:")
807 for fmt, count in stats["by_format"].items():
808 print(f" {fmt}: {count}")
810 else:
811 print(f"Unknown marketplace command: {sub}")
812 print("Try: agentos marketplace --help")
815def _run_enterprise(args: list[str]):
816 """Enterprise CLI dispatcher."""
818 if not args or args[0] in ("help", "--help", "-h"):
819 print("AgentOS Enterprise CLI\n")
820 print("Subcommands:")
821 print(" api-key create|list|revoke|stats API Key management")
822 print(" tenant create|list|stats Multi-tenant management")
823 print(" audit stats|recent|export Audit logging")
824 return
826 sub = args[0]
827 from agentos.enterprise import (
828 APIKeyManager,
829 AuditLogger,
830 TenantManager,
831 )
833 if sub == "api-key":
834 _run_enterprise_api_key(args[1:], APIKeyManager())
835 elif sub == "tenant":
836 _run_enterprise_tenant(args[1:], TenantManager())
837 elif sub == "audit":
838 _run_enterprise_audit(args[1:], AuditLogger())
839 else:
840 print(f"Unknown enterprise subcommand: {sub}")
841 print("Try: agentos enterprise --help")
844def _run_enterprise_api_key(args: list[str], mgr):
845 from agentos.enterprise import KeyCreateRequest, KeyScope
847 if not args:
848 print("Usage: agentos enterprise api-key <create|list|revoke|stats>")
849 return
850 cmd = args[0]
851 if cmd == "create":
852 name = args[1] if len(args) > 1 else "cli-key"
853 result = mgr.create_key(
854 KeyCreateRequest(name=name, scopes=[KeyScope.AGENT_RUN, KeyScope.READ])
855 )
856 print(f"Key created: {result.key_id}")
857 print(f"Plaintext (only shown once): {result.plaintext_key}")
858 print(f"Prefix: {result.key_prefix}")
859 print(f"Scopes: {[s.value for s in result.scopes]}")
860 elif cmd == "list":
861 keys = mgr.list_keys()
862 if not keys:
863 print("No API keys.")
864 return
865 print(f"{'ID':<28s} {'Name':<24s} {'Status':<10s} {'Usage':<8s}")
866 print("-" * 72)
867 for k in keys:
868 status = "revoked" if k.revoked else "active"
869 print(f"{k.key_id:<28s} {k.name:<24s} {status:<10s} {k.usage_count:<8d}")
870 elif cmd == "revoke":
871 if len(args) < 2:
872 print("Usage: agentos enterprise api-key revoke <key_id>")
873 return
874 ok = mgr.revoke_key(args[1])
875 print(f"{'Revoked' if ok else 'Not found or already revoked'}: {args[1]}")
876 elif cmd == "stats":
877 stats = mgr.stats()
878 print(
879 f"Total: {stats['total']} Active: {stats['active']} Revoked: {stats['revoked']} "
880 f"Total usage: {stats['total_usage_count']}"
881 )
882 else:
883 print(f"Unknown api-key command: {cmd}")
886def _run_enterprise_tenant(args: list[str], mgr):
887 from agentos.enterprise import TenantTier
889 if not args:
890 print("Usage: agentos enterprise tenant <create|list|stats>")
891 return
892 cmd = args[0]
893 if cmd == "create":
894 name = args[1] if len(args) > 1 else "default"
895 tier_str = args[2] if len(args) > 2 else "free"
896 tier = (
897 TenantTier(tier_str) if tier_str in [t.value for t in TenantTier] else TenantTier.FREE
898 )
899 tenant = mgr.create_tenant(name=name, tier=tier)
900 print(f"Tenant created: {tenant.tenant_id}")
901 print(f"Name: {tenant.name} Tier: {tenant.tier.value}")
902 elif cmd == "list":
903 tenants = mgr.list_tenants()
904 if not tenants:
905 print("No tenants.")
906 return
907 print(f"{'ID':<20s} {'Name':<20s} {'Tier':<12s} {'Status':<10s}")
908 print("-" * 64)
909 for t in tenants:
910 print(f"{t.tenant_id:<20s} {t.name:<20s} {t.tier.value:<12s} {t.status.value:<10s}")
911 elif cmd == "stats":
912 stats = mgr.stats()
913 print(f"Total tenants: {stats['total']}")
914 print(f"By tier: {stats['by_tier']}")
915 print(f"By status: {stats['by_status']}")
916 else:
917 print(f"Unknown tenant command: {cmd}")
920def _run_enterprise_audit(args: list[str], logger):
921 if not args:
922 print("Usage: agentos enterprise audit <stats|recent|export>")
923 return
924 cmd = args[0]
925 if cmd == "stats":
926 stats = logger.stats()
927 print(f"Total events: {stats['total_events']}")
928 print(f"By category: {stats['by_category']}")
929 print(f"By severity: {stats['by_severity']}")
930 elif cmd == "recent":
931 n = int(args[1]) if len(args) > 1 else 10
932 events = logger.get_recent(n)
933 if not events:
934 print("No audit events.")
935 return
936 for e in events:
937 ts = __import__("time").strftime("%H:%M:%S", __import__("time").gmtime(e.timestamp))
938 print(f"[{ts}] {e.category.value:8s} {e.action:24s} {e.status}")
939 elif cmd == "export":
940 fmt = args[1] if len(args) > 1 else "json"
941 if fmt == "csv":
942 print(logger.export_csv()[:2000])
943 else:
944 print(logger.export_json()[:2000])
945 print("... (truncated to 2000 chars)")
946 else:
947 print(f"Unknown audit command: {cmd}")
950def _run_demo(verbose: bool):
951 from agentos.agent import AgentConfig, ToolAgent, ToolExecutor
952 from agentos.llm import Tool, ToolParameter, create_provider
954 def get_weather(city: str) -> str:
955 data = {
956 "北京": "北京:晴,22°C,湿度 35%,东北风 3 级",
957 "上海": "上海:多云转阴,28°C,湿度 70%,东南风 2 级",
958 "深圳": "深圳:雷阵雨,31°C,湿度 85%",
959 }
960 return data.get(city, f"{city}: 数据暂缺")
962 def get_stock(symbol: str) -> str:
963 prices = {
964 "AAPL": "$220.50 (+1.2%)",
965 "TSLA": "$248.30 (-0.8%)",
966 }
967 return prices.get(symbol.upper(), f"{symbol}: 未找到")
969 executor = ToolExecutor()
970 executor.register(
971 Tool.from_function(
972 "get_weather",
973 "获取城市天气",
974 {
975 "city": ToolParameter(type="string", description="城市名"),
976 },
977 ),
978 get_weather,
979 )
980 executor.register(
981 Tool.from_function(
982 "get_stock_price",
983 "获取股票价格",
984 {
985 "symbol": ToolParameter(type="string", description="股票代码,如 AAPL"),
986 },
987 ),
988 get_stock,
989 )
991 if os.getenv("OPENAI_API_KEY"):
992 provider = create_provider("openai")
993 elif os.getenv("DEEPSEEK_API_KEY"):
994 provider = create_provider("deepseek")
995 else:
996 print("\n ⚠️ Mock 演示 — 配置 API Key 获取真实 AI 响应\n")
997 print(" 运行: agentos init\n")
998 provider = MockLLMProvider(
999 [
1000 MockLLMProvider.tool_response(
1001 "get_weather",
1002 {"city": "北京"},
1003 tool_call_id="tc_w1",
1004 ),
1005 MockLLMProvider.text_response(
1006 "北京目前天气晴,气温 22°C,湿度 35%,东北风 3 级。"
1007 "适合户外活动,建议带薄外套。"
1008 ),
1009 ]
1010 )
1012 agent = ToolAgent(
1013 provider=provider,
1014 tool_executor=executor,
1015 config=AgentConfig(verbose=verbose, temperature=0.0),
1016 system_prompt="你是一个天气助手。用工具获取天气/股票信息后直接回答。",
1017 )
1019 result = agent.run("北京天气怎么样?")
1020 print("\nTask: 北京天气怎么样?")
1021 print(f"Steps: {result.total_steps} | Time: {(result.total_duration_ms/1000):.1f}s")
1022 print(f"Tokens: {result.total_tokens} | Cost: ${result.total_cost_usd:.4f}")
1023 print(f"Answer: {result.final_answer}")
1026if __name__ == "__main__":
1027 main()