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
« 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"""
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 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")]
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)
346 cmd = args[0]
347 if cmd == "init":
348 from agentos.cli.init import init_cli
349 init_cli(args)
350 return
352 if cmd == "config-panel":
353 from agentos.cli.config_panel import start_panel
354 start_panel()
355 return
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
370 if cmd == "version":
371 from agentos import __version__
372 print(f"AgentOS v{__version__}")
373 return
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
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
405 if cmd == "dashboard":
406 from agentos.dashboard.server import start_dashboard
407 print("Starting AgentOS Dashboard...")
408 start_dashboard()
409 return
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
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
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
441 if cmd == "enterprise":
442 _run_enterprise(args[1:])
443 return
445 if cmd == "marketplace":
446 _run_marketplace(args[1:])
447 return
449 if cmd == "rollback":
450 from agentos.cli.rollback import rollback_cli
451 sys.exit(rollback_cli(args[1:]))
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
479 if cmd == "daemon":
480 from agentos.server.daemon import daemon_main
481 sys.exit(daemon_main(args[1:]))
483 if cmd == "hello":
484 _run_hello()
485 return
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()
498 try:
499 choice = input(" 请输入数字 (1-3) [1]: ").strip() or "1"
500 except (EOFError, KeyboardInterrupt):
501 choice = "1"
503 if choice == "3":
504 _run_hello()
505 elif choice == "2":
506 _run_file_demo(verbose)
507 else:
508 _run_demo(verbose)
509 return
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()
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
548def _run_marketplace(args: list[str]):
549 """Skill Marketplace CLI dispatcher."""
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
564 from agentos.marketplace import SkillRegistry, InstallResult
566 registry = SkillRegistry()
567 sub = args[0]
568 rest = args[1:]
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}")
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}")
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}")
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()
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}")
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.")
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}")
684 else:
685 print(f"Unknown marketplace command: {sub}")
686 print("Try: agentos marketplace --help")
689def _run_enterprise(args: list[str]):
690 """Enterprise CLI dispatcher."""
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
700 sub = args[0]
701 from agentos.enterprise import (
702 APIKeyManager, TenantManager, AuditLogger,
703 KeyCreateRequest, KeyScope, TenantTier,
704 )
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")
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}")
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}")
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}")
813def _run_demo(verbose: bool):
814 from agentos.llm import create_provider, Tool, ToolParameter
815 from agentos.agent import ToolAgent, ToolExecutor, AgentConfig
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}: 数据暂缺")
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}: 未找到")
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 )
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 ])
863 agent = ToolAgent(
864 provider=provider,
865 tool_executor=executor,
866 config=AgentConfig(verbose=verbose, temperature=0.0),
867 system_prompt="你是一个天气助手。用工具获取天气/股票信息后直接回答。",
868 )
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}")
877if __name__ == "__main__":
878 main()