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