MikroTik RouterOS Skill 通过 API 连接和管理 MikroTik RouterOS 设备。 功能 - 查看设备状态(系统信息、CPU、内存、存储) - 查看防火墙规则(filter、NAT、mangle) - 查看网络配置(接口、IP 地址、路由、DNS) - 执行自定义 RouterOS 命令 - 支持多设备连接 配置 方式一:TOOLS.md(推荐) 在 中添加设备信息: 密码格式说明 : - 空密码:写 、 、 或留空 - 有密码:直接写密码字符串 方式二:环境变量 优先级 :环境变量 TOOLS.md 默认值 用法 📊 设备状态 🔥 防火墙 🔌 网络接口 📋 DHCP 📡 ARP 表 🔐 WireGuard 👤 用户 📝 日志 🔧 服务 📈 流量统计 🔌 接口详情 🏷️ VLAN 🌉 桥接 📊 队列/带宽 🌐 路由 🌡️ 系统健康 📅 计划任务 📡 邻居设备 🔗 活动连接 🏓 Ping 测试 📡 网络扫描 🎯 自定义命令 🖥️ 多设备支持 如果配置了多个设备,可以在命令中指定设备名称: 依赖 - Python 3.6+ - 设备 API 已启用(默认端口 8728) - 网络可达 文件结构 示例响应 注意事项 1. 确保 RouterOS 的 API 服务已启用( 查看) 2. 默认端口 8728,S…

\n matches = re.findall(device_pattern, section_text, re.MULTILINE)\n \n for name, host, username, password in matches:\n device_key = name.strip().lower()\n # 处理\"空密码\"的情况\n pwd = password.strip()\n if pwd.lower() in ['空密码', '无密码', 'none', 'null', '']:\n pwd = ''\n \n devices[device_key] = {\n 'host': host.strip(),\n 'username': username.strip(),\n 'password': pwd\n }\n except Exception as e:\n print(f\"⚠️ 读取 TOOLS.md 失败:{e}\")\n \n # 3. 返回请求的设备配置\n if device_name:\n # 优先返回环境变量配置的 default\n if device_name.lower() in ['default', '默认'] and 'default' in devices:\n return devices['default']\n return devices.get(device_name.lower())\n \n # 没有指定设备时,返回第一个可用的\n if 'default' in devices:\n return devices['default']\n if devices:\n return list(devices.values())[0]\n \n return None\n\n\ndef format_status(api, quick):\n \"\"\"格式化设备状态\"\"\"\n identity = quick.system.get_identity()\n resource = quick.system.get_resource()\n \n # 获取 RouterBOARD 信息(正确解读固件字段)\n routerboard = quick.system.get_version()\n \n lines = [\n \"📡 MikroTik RouterOS 设备状态\",\n \"=\" * 60,\n f\" 设备名:{identity.get('name', 'N/A')}\",\n f\" RouterOS: {resource.get('version', 'N/A')}\",\n f\" 型号:{routerboard.get('model', 'N/A')} ({routerboard.get('board-name', 'N/A')})\",\n f\" 运行时间:{resource.get('uptime', 'N/A')}\",\n f\" CPU: {resource.get('cpu', 'N/A')} @ {resource.get('cpu-frequency', 'N/A')}MHz\",\n f\" CPU 负载:{resource.get('cpu-load', 'N/A')}%\",\n f\" 内存:{int(resource.get('free-memory', 0))/1024/1024:.1f}MB / \"\n f\"{int(resource.get('total-memory', 1))/1024/1024:.1f}MB\",\n f\" 存储:{int(resource.get('free-hdd-space', 0))/1024/1024:.1f}MB / \"\n f\"{int(resource.get('total-hdd-space', 1))/1024/1024:.1f}MB\",\n \"=\" * 60,\n ]\n \n # 显示固件信息(正确解读字段)\n if routerboard and routerboard.get('routerboard') == 'true':\n lines.append(f\"\\n🔧 RouterBOARD 信息:\")\n lines.append(f\" 序列号:{routerboard.get('serial-number', 'N/A')}\")\n # upgrade-firmware 是当前实际使用的固件版本\n firmware = routerboard.get('upgrade-firmware', routerboard.get('current-firmware', 'N/A'))\n lines.append(f\" 固件版本:{firmware}\")\n \n # 接口状态\n lines.append(\"\\n🔌 网络接口:\")\n interfaces = quick.network.get_interfaces()\n for iface in interfaces:\n name = iface.get('name', 'unknown')\n running = iface.get('running', 'false') == 'true'\n status = \"✅\" if running else \"❌\"\n lines.append(f\" {status} {name}\")\n \n # IP 地址\n lines.append(\"\\n🌐 IP 地址:\")\n addresses = quick.network.get_ip_addresses()\n for addr in addresses:\n lines.append(f\" - {addr.get('address', 'N/A')} on {addr.get('interface', 'N/A')}\")\n \n # 默认路由\n lines.append(\"\\n🛤️ 默认路由:\")\n routes = quick.network.get_routes()\n for route in routes:\n if route.get('dst-address') == '0.0.0.0/0':\n lines.append(f\" - 默认网关:{route.get('gateway', 'N/A')}\")\n \n return \"\\n\".join(lines)\n\n\ndef format_firewall(api, quick):\n \"\"\"格式化防火墙信息\"\"\"\n lines = [\n \"🔥 MikroTik 防火墙配置\",\n \"=\" * 60,\n ]\n \n # 过滤规则\n lines.append(\"\\n📋 过滤规则:\")\n rules = quick.firewall.get_filter_rules()\n if rules:\n for i, rule in enumerate(rules, 1):\n chain = rule.get('chain', 'N/A')\n action = rule.get('action', 'N/A')\n disabled = rule.get('disabled', '') == 'true'\n comment = rule.get('comment', '')\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} [{i}] {chain}: {action}\" + (f\" ({comment})\" if comment else \"\"))\n else:\n lines.append(\" (无规则)\")\n \n # NAT 规则\n lines.append(\"\\n🔄 NAT 规则:\")\n rules = quick.firewall.get_nat_rules()\n if rules:\n for i, rule in enumerate(rules, 1):\n chain = rule.get('chain', 'N/A')\n action = rule.get('action', 'N/A')\n to_addr = rule.get('to-addresses', '')\n comment = rule.get('comment', '')\n line = f\" [{i}] {chain}: {action}\"\n if to_addr:\n line += f\" → {to_addr}\"\n if comment:\n line += f\" ({comment})\"\n lines.append(line)\n else:\n lines.append(\" (无规则)\")\n \n # 活动连接\n lines.append(\"\\n🔗 活动连接数:检查中...\")\n \n return \"\\n\".join(lines)\n\n\ndef format_interfaces(api, quick):\n \"\"\"格式化接口信息\"\"\"\n lines = [\n \"🔌 网络接口\",\n \"=\" * 60,\n ]\n \n interfaces = quick.network.get_interfaces()\n for iface in interfaces:\n name = iface.get('name', 'unknown')\n running = iface.get('running', 'false') == 'true'\n mtu = iface.get('mtu', 'N/A')\n mac = iface.get('mac-address', 'N/A')\n status = \"✅\" if running else \"❌\"\n lines.append(f\" {status} {name} (MTU: {mtu}, MAC: {mac})\")\n \n return \"\\n\".join(lines)\n\n\ndef format_dhcp(api, quick):\n \"\"\"格式化 DHCP 信息\"\"\"\n lines = [\n \"📋 DHCP 配置\",\n \"=\" * 60,\n ]\n \n # DHCP 服务器\n lines.append(\"\\n🖥️ DHCP 服务器:\")\n servers = quick.network.get_dhcp_servers()\n for srv in servers:\n name = srv.get('name', 'N/A')\n iface = srv.get('interface', 'N/A')\n lines.append(f\" - {name} on {iface}\")\n \n # DHCP 租约\n lines.append(\"\\n📝 DHCP 租约:\")\n leases = quick.network.get_dhcp_leases()\n if leases:\n for i, lease in enumerate(leases[:20], 1): # 最多显示 20 条\n ip = lease.get('address', 'N/A')\n mac = lease.get('mac-address', 'N/A')\n host = lease.get('host-name', 'N/A')\n status = lease.get('status', 'N/A')\n lines.append(f\" {i}. {ip} - {mac}\" + (f\" ({host})\" if host else \"\") + f\" [{status}]\")\n if len(leases) > 20:\n lines.append(f\" ... 还有 {len(leases) - 20} 条租约\")\n else:\n lines.append(\" (无租约)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_clients(api, quick):\n \"\"\"格式化无线客户端列表 (CAPsMAN)\"\"\"\n lines = [\n \"📶 无线客户端 (CAPsMAN)\",\n \"=\" * 60,\n ]\n \n # 获取 CAPsMAN 注册表\n reg = api.run_command('/caps-man/registration-table/print')\n \n if reg:\n lines.append(f\"\\n✅ 已连接 {len(reg)} 个无线客户端:\\n\")\n for i, client in enumerate(reg, 1):\n mac = client.get('mac-address', 'N/A')\n ssid = client.get('ssid', 'N/A')\n iface = client.get('interface', 'N/A')\n signal = client.get('rx-signal', 'N/A')\n tx_rate = client.get('tx-rate', 'N/A')\n rx_rate = client.get('rx-rate', 'N/A')\n uptime = client.get('uptime', 'N/A')\n last_ip = client.get('last-ip', 'N/A')\n packets_tx = client.get('packets', 'N/A')\n bytes_tx = client.get('bytes', 'N/A')\n \n # 解析流量统计\n traffic_info = \"\"\n if packets_tx != 'N/A' and ',' in str(packets_tx):\n pkt = str(packets_tx).split(',')\n bytes_list = str(bytes_tx).split(',') if ',' in str(bytes_tx) else ['N/A', 'N/A']\n # 内联字节格式化\n def fmt_b(b):\n if not b.isdigit():\n return 'N/A'\n b = int(b)\n if b >= 1024**3:\n return f\"{b/1024**3:.1f}GB\"\n elif b >= 1024**2:\n return f\"{b/1024**2:.1f}MB\"\n elif b >= 1024:\n return f\"{b/1024:.1f}KB\"\n else:\n return f\"{b}B\"\n tx_b = fmt_b(bytes_list[0])\n rx_b = fmt_b(bytes_list[1]) if len(bytes_list) > 1 else 'N/A'\n traffic_info = f\"\\n 流量:TX {tx_b} / RX {rx_b}\"\n \n # 信号强度评级\n signal_rating = \"\"\n try:\n sig = int(signal.replace(' dBm', '').replace('-', '')) if signal != 'N/A' else 0\n if sig \u003c= 35:\n signal_rating = \"⭐⭐⭐⭐⭐\"\n elif sig \u003c= 45:\n signal_rating = \"⭐⭐⭐⭐\"\n elif sig \u003c= 55:\n signal_rating = \"⭐⭐⭐\"\n elif sig \u003c= 65:\n signal_rating = \"⭐⭐\"\n else:\n signal_rating = \"⭐\"\n except:\n pass\n \n lines.append(f\" 【客户端 {i}】\")\n lines.append(f\" MAC: {mac}\")\n lines.append(f\" SSID: {ssid} | 接口:{iface}\")\n lines.append(f\" 信号:{signal} {signal_rating}\")\n lines.append(f\" 速率:TX {tx_rate} | RX {rx_rate}\")\n lines.append(f\" 连接时间:{uptime}\")\n lines.append(f\" IP 地址:{last_ip}{traffic_info}\")\n lines.append(\"\")\n else:\n lines.append(\"\\n❌ 当前没有无线客户端连接\")\n lines.append(\"\\n💡 提示:\")\n lines.append(\" 1. 确认 CAPsMAN 已启用 (/caps-man/manager)\")\n lines.append(\" 2. 确认 AP 已注册为 CAP (/interface/wireless/cap)\")\n lines.append(\" 3. 检查无线配置模板 (/caps-man/configuration)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_arp(api, quick):\n \"\"\"格式化 ARP 表\"\"\"\n lines = [\n \"📋 ARP 表\",\n \"=\" * 60,\n ]\n \n arp_entries = quick.network.get_arp()\n if arp_entries:\n lines.append(f\"\\n共 {len(arp_entries)} 条记录:\\n\")\n for i, entry in enumerate(arp_entries[:30], 1): # 最多显示 30 条\n ip = entry.get('address', 'N/A')\n mac = entry.get('mac-address', 'N/A')\n iface = entry.get('interface', 'N/A')\n lines.append(f\" {i}. {ip} → {mac} ({iface})\")\n if len(arp_entries) > 30:\n lines.append(f\" ... 还有 {len(arp_entries) - 30} 条记录\")\n else:\n lines.append(\" (无记录)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_wireguard(api, quick):\n \"\"\"格式化 WireGuard 信息\"\"\"\n lines = [\n \"🔐 WireGuard 配置\",\n \"=\" * 60,\n ]\n \n peers = quick.network.get_wireguard_peers()\n if peers:\n lines.append(f\"\\n共 {len(peers)} 个对等体:\\n\")\n for i, peer in enumerate(peers, 1):\n name = peer.get('name', 'N/A')\n pubkey = peer.get('public-key', 'N/A')[:20] + '...' if peer.get('public-key') else 'N/A'\n endpoint = peer.get('endpoint', 'N/A')\n allowed = peer.get('allowed-address', 'N/A')\n lines.append(f\" {i}. {name}\")\n lines.append(f\" 公钥:{pubkey}\")\n lines.append(f\" 端点:{endpoint}\")\n lines.append(f\" 允许:{allowed}\")\n else:\n lines.append(\" (无 WireGuard 对等体)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_users(api, quick):\n \"\"\"格式化用户信息\"\"\"\n lines = [\n \"👤 用户配置\",\n \"=\" * 60,\n ]\n \n # 系统用户\n lines.append(\"\\n🔐 系统用户:\")\n users = quick.system.get_users()\n for user in users:\n name = user.get('name', 'N/A')\n group = user.get('group', 'N/A')\n disabled = user.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} {name} ({group})\")\n \n # PPP 用户\n ppp_users = quick.user.get_ppp_users()\n if ppp_users:\n lines.append(f\"\\n📞 PPP 用户 ({len(ppp_users)}):\")\n for user in ppp_users[:10]:\n name = user.get('name', 'N/A')\n service = user.get('service', 'N/A')\n lines.append(f\" - {name} ({service})\")\n if len(ppp_users) > 10:\n lines.append(f\" ... 还有 {len(ppp_users) - 10} 个用户\")\n \n return \"\\n\".join(lines)\n\n\ndef format_logs(api, quick):\n \"\"\"格式化日志信息\"\"\"\n lines = [\n \"📝 系统日志 (最近 20 条)\",\n \"=\" * 60,\n ]\n \n logs = quick.system.get_recent_logs(20)\n if logs:\n for log in logs:\n time = log.get('time', 'N/A')\n topics = log.get('topics', 'N/A')\n message = log.get('message', 'N/A')\n lines.append(f\" [{time}] {topics}: {message}\")\n else:\n lines.append(\" (无日志)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_services(api, quick):\n \"\"\"格式化系统服务\"\"\"\n lines = [\n \"🔧 系统服务\",\n \"=\" * 60,\n ]\n \n services = quick.system.get_services()\n for svc in services:\n name = svc.get('name', 'N/A')\n port = svc.get('port', 'N/A')\n disabled = svc.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} {name} (端口:{port})\")\n \n return \"\\n\".join(lines)\n\n\ndef format_traffic(api, quick):\n \"\"\"格式化接口流量统计\"\"\"\n lines = [\n \"📈 接口流量统计\",\n \"=\" * 60,\n ]\n \n stats = quick.network.get_interface_stats()\n \n if not stats:\n lines.append(\" (无数据)\")\n return \"\\n\".join(lines)\n \n # 表头\n lines.append(\"\")\n lines.append(f\" {'接口':\u003c20} {'接收 ↓':\u003c15} {'发送 ↑':\u003c15} {'包数':\u003c12} {'错误/丢弃'}\")\n lines.append(\" \" + \"-\" * 70)\n \n for iface in stats:\n name = iface.get('name', 'unknown')[:18]\n rx_bytes = int(iface.get('rx-byte', 0))\n tx_bytes = int(iface.get('tx-byte', 0))\n rx_packets = int(iface.get('rx-packet', 0))\n tx_packets = int(iface.get('tx-packet', 0))\n rx_errors = int(iface.get('rx-error', 0))\n tx_errors = int(iface.get('tx-error', 0))\n rx_drops = int(iface.get('rx-drop', 0))\n tx_drops = int(iface.get('tx-drop', 0))\n \n # 格式化流量(转换为 MB/GB)\n def format_bytes(b):\n if b >= 1024**3:\n return f\"{b/1024**3:.1f}GB\"\n elif b >= 1024**2:\n return f\"{b/1024**2:.1f}MB\"\n elif b >= 1024:\n return f\"{b/1024:.1f}KB\"\n else:\n return f\"{b}B\"\n \n rx_str = format_bytes(rx_bytes)\n tx_str = format_bytes(tx_bytes)\n packets_str = f\"{rx_packets + tx_packets:,}\"\n errors_str = f\"{rx_errors + tx_errors}/{rx_drops + tx_drops}\"\n \n lines.append(f\" {name:\u003c20} {rx_str:>14} {tx_str:>14} {packets_str:>12} {errors_str}\")\n \n lines.append(\" \" + \"-\" * 70)\n lines.append(f\"\\n总计:{len(stats)} 个接口\")\n \n return \"\\n\".join(lines)\n\n\ndef format_interface_detail(api, quick, interface_name: str = ''):\n \"\"\"格式化接口详细信息\"\"\"\n lines = [\n f\"🔌 接口详情\" + (f\": {interface_name}\" if interface_name else \"\"),\n \"=\" * 60,\n ]\n \n if interface_name:\n stats = quick.network.get_interface_stats(interface_name)\n else:\n stats = quick.network.get_interface_stats()\n \n if not stats:\n lines.append(f\" ❌ 未找到接口:{interface_name}\" if interface_name else \" (无数据)\")\n return \"\\n\".join(lines)\n \n for iface in stats:\n name = iface.get('name', 'unknown')\n iface_type = iface.get('type', 'N/A')\n running = iface.get('running', 'false') == 'true'\n mtu = iface.get('mtu', 'N/A')\n mac = iface.get('mac-address', 'N/A')\n rx_bytes = int(iface.get('rx-byte', 0))\n tx_bytes = int(iface.get('tx-byte', 0))\n \n lines.append(f\"\\n📌 {name}\")\n lines.append(f\" 类型:{iface_type}\")\n lines.append(f\" 状态:{'✅ 运行中' if running else '❌ 已关闭'}\")\n lines.append(f\" MTU: {mtu}\")\n lines.append(f\" MAC: {mac}\")\n lines.append(f\" 接收:{rx_bytes / 1024 / 1024:.2f} MB ({int(iface.get('rx-packet', 0)):,} 包)\")\n lines.append(f\" 发送:{tx_bytes / 1024 / 1024:.2f} MB ({int(iface.get('tx-packet', 0)):,} 包)\")\n \n # VLAN 信息\n if iface_type == 'vlan':\n vlan_id = iface.get('vlan-id', 'N/A')\n interface = iface.get('interface', 'N/A')\n lines.append(f\" VLAN ID: {vlan_id} (on {interface})\")\n \n # Bridge 信息\n if iface_type == 'bridge':\n lines.append(f\" 桥接模式\")\n \n # WireGuard 信息\n if iface_type == 'wireguard':\n lines.append(f\" WireGuard VPN 接口\")\n \n return \"\\n\".join(lines)\n\n\ndef format_vlan(api, quick):\n \"\"\"格式化 VLAN 配置\"\"\"\n lines = [\n \"🏷️ VLAN 配置\",\n \"=\" * 60,\n ]\n \n vlans = quick.network.get_vlan()\n if vlans:\n for vlan in vlans:\n name = vlan.get('name', 'N/A')\n vlan_id = vlan.get('vlan-id', 'N/A')\n interface = vlan.get('interface', 'N/A')\n running = vlan.get('running', 'false') == 'true'\n status = \"✅\" if running else \"❌\"\n lines.append(f\" {status} {name} (ID: {vlan_id}, on {interface})\")\n else:\n lines.append(\" (无 VLAN 配置)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_bridge(api, quick):\n \"\"\"格式化桥接配置\"\"\"\n lines = [\n \"🌉 桥接配置\",\n \"=\" * 60,\n ]\n \n bridges = quick.network.get_bridge()\n if bridges:\n for bridge in bridges:\n name = bridge.get('name', 'N/A')\n running = bridge.get('running', 'false') == 'true'\n status = \"✅\" if running else \"❌\"\n lines.append(f\" {status} {name}\")\n \n # 桥接端口\n lines.append(\"\\n🔌 桥接端口:\")\n ports = quick.network.get_bridge_ports()\n for port in ports[:20]: # 最多显示 20 个\n bridge_name = port.get('bridge', 'N/A')\n interface = port.get('interface', 'N/A')\n hw = port.get('hw', 'false') == 'true'\n lines.append(f\" - {interface} → {bridge_name}\" + (\" (硬件转发)\" if hw else \"\"))\n if len(ports) > 20:\n lines.append(f\" ... 还有 {len(ports) - 20} 个端口\")\n else:\n lines.append(\" (无桥接配置)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_health(api, quick):\n \"\"\"格式化系统健康信息\"\"\"\n lines = [\n \"🌡️ 系统健康\",\n \"=\" * 60,\n ]\n \n # 系统资源\n resource = quick.system.get_resource()\n lines.append(f\"\\n💻 系统资源:\")\n lines.append(f\" CPU 负载:{resource.get('cpu-load', 'N/A')}%\")\n \n # 内存\n free_mem = int(resource.get('free-memory', 0)) / 1024 / 1024\n total_mem = int(resource.get('total-memory', 1)) / 1024 / 1024\n used_mem = total_mem - free_mem\n mem_percent = (used_mem / total_mem * 100) if total_mem > 0 else 0\n lines.append(f\" 内存使用:{used_mem:.1f}MB / {total_mem:.1f}MB ({mem_percent:.1f}%)\")\n \n # 存储\n free_hdd = int(resource.get('free-hdd-space', 0)) / 1024 / 1024\n total_hdd = int(resource.get('total-hdd-space', 1)) / 1024 / 1024\n used_hdd = total_hdd - free_hdd\n hdd_percent = (used_hdd / total_hdd * 100) if total_hdd > 0 else 0\n lines.append(f\" 存储使用:{used_hdd:.1f}MB / {total_hdd:.1f}MB ({hdd_percent:.1f}%)\")\n \n # 硬件健康状态(温度、电压、风扇)\n health = quick.system.get_health()\n if health:\n lines.append(f\"\\n🔧 硬件状态:\")\n \n # 温度\n temperature = health.get('temperature', '')\n if temperature:\n temp_value = temperature.replace('°C', '').strip()\n try:\n temp_num = float(temp_value)\n if temp_num > 70:\n temp_icon = \"🔥\"\n elif temp_num > 50:\n temp_icon = \"⚠️\"\n else:\n temp_icon = \"✅\"\n lines.append(f\" {temp_icon} 温度:{temperature}\")\n except:\n lines.append(f\" 🌡️ 温度:{temperature}\")\n \n # 电压\n voltage = health.get('voltage', '')\n if voltage:\n lines.append(f\" ⚡ 电压:{voltage}\")\n \n # 风扇状态\n psu1_state = health.get('psu1-state', '')\n psu2_state = health.get('psu2-state', '')\n fan1_state = health.get('fan1-state', '')\n fan2_state = health.get('fan2-state', '')\n \n if psu1_state:\n psu1_icon = \"✅\" if psu1_state == 'ok' else \"❌\"\n lines.append(f\" {psu1_icon} 电源 1: {psu1_state}\")\n if psu2_state:\n psu2_icon = \"✅\" if psu2_state == 'ok' else \"❌\"\n lines.append(f\" {psu2_icon} 电源 2: {psu2_state}\")\n if fan1_state:\n fan1_icon = \"✅\" if fan1_state == 'ok' else \"❌\"\n lines.append(f\" {fan1_icon} 风扇 1: {fan1_state}\")\n if fan2_state:\n fan2_icon = \"✅\" if fan2_state == 'ok' else \"❌\"\n lines.append(f\" {fan2_icon} 风扇 2: {fan2_state}\")\n \n # 其他健康指标\n for key, value in health.items():\n if key not in ['temperature', 'voltage', 'psu1-state', 'psu2-state', 'fan1-state', 'fan2-state']:\n if 'temperature' in key.lower() or 'voltage' in key.lower() or 'fan' in key.lower():\n lines.append(f\" 🔧 {key}: {value}\")\n else:\n lines.append(f\"\\n🔧 硬件状态:不支持或未配置\")\n \n # 运行时间\n uptime = resource.get('uptime', 'N/A')\n lines.append(f\"\\n⏱️ 运行时间:{uptime}\")\n \n # 看门狗状态\n watchdog = quick.system.get_watchdog()\n if watchdog:\n lines.append(f\"\\n🐕 看门狗:\")\n enabled = watchdog.get('watchdog-timer', '') != 'no'\n lines.append(f\" 状态:{'✅ 已启用' if enabled else '❌ 已禁用'}\")\n if watchdog.get('watchdog-timer', '') != 'no':\n lines.append(f\" 超时:{watchdog.get('watchdog-timer', 'N/A')}\")\n \n return \"\\n\".join(lines)\n\n\ndef format_scheduler(api, quick):\n \"\"\"格式化计划任务\"\"\"\n lines = [\n \"📅 计划任务\",\n \"=\" * 60,\n ]\n \n schedulers = quick.system.get_scheduler()\n if schedulers:\n lines.append(f\"\\n共 {len(schedulers)} 个任务:\\n\")\n for i, sched in enumerate(schedulers[:20], 1):\n name = sched.get('name', 'N/A')\n interval = sched.get('interval', 'N/A')\n on_event = sched.get('on-event', 'N/A')\n disabled = sched.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} [{i}] {name}\")\n lines.append(f\" 间隔:{interval}\")\n lines.append(f\" 执行:{on_event}\")\n if len(schedulers) > 20:\n lines.append(f\" ... 还有 {len(schedulers) - 20} 个任务\")\n else:\n lines.append(\" (无计划任务)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_neighbors(api, quick):\n \"\"\"格式化邻居设备发现\"\"\"\n lines = [\n \"📡 邻居设备\",\n \"=\" * 60,\n ]\n \n neighbors = quick.network.get_neighbors()\n if neighbors:\n lines.append(f\"\\n共 {len(neighbors)} 个邻居设备:\\n\")\n for i, nbr in enumerate(neighbors[:20], 1):\n identity = nbr.get('identity', 'N/A')\n platform = nbr.get('platform', 'N/A')\n address = nbr.get('address', 'N/A')\n interface = nbr.get('interface', 'N/A')\n mac = nbr.get('mac-address', 'N/A')\n lines.append(f\" [{i}] {identity}\")\n lines.append(f\" 型号:{platform}\")\n lines.append(f\" 地址:{address}\")\n lines.append(f\" 接口:{interface}\")\n if mac != 'N/A':\n lines.append(f\" MAC: {mac}\")\n if len(neighbors) > 20:\n lines.append(f\" ... 还有 {len(neighbors) - 20} 个设备\")\n else:\n lines.append(\" (未发现邻居设备)\")\n \n return \"\\n\".join(lines)\n\n\ndef format_scan(api, quick):\n \"\"\"格式化网络扫描结果(从 LattePanda 本地独立扫描,不依赖 API)\"\"\"\n import sys\n import os\n sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'mikrotik-api'))\n from scanner import MikroTikScanner\n \n lines = [\n \"📡 网络扫描\",\n \"=\" * 60,\n ]\n \n print(\"🔍 从 LattePanda 本地扫描(不依赖已知设备)...\\n\")\n \n # 使用独立扫描器\n scanner = MikroTikScanner(timeout=2.0)\n devices = scanner.scan()\n \n # 获取 LattePanda 本机 IP(用于排除,不扫描自己)\n self_ips = []\n try:\n result = subprocess.run(['hostname', '-I'], capture_output=True, text=True, timeout=5)\n self_ips = result.stdout.strip().split()\n except:\n pass\n \n # 排除 LattePanda 本机 IP\n if self_ips:\n original_count = len(devices)\n devices = [dev for dev in devices if dev.get('ip') not in self_ips]\n if original_count > len(devices):\n print(f\"\\nℹ️ 排除 {original_count - len(devices)} 个本机 IP\")\n # 更新 scanner 的设备列表\n scanner.discovered_devices = devices\n \n # 如果已连接 API,尝试获取型号信息(可选增强)\n try:\n if api and api.connected and devices:\n print(\"\\n🔍 从 API 获取设备型号(可选)...\")\n neighbors = quick.network.get_neighbors()\n neighbor_map = {}\n for nbr in neighbors:\n mac = nbr.get('mac-address', '').upper()\n if mac:\n board = nbr.get('board', '')\n neighbor_map[mac] = {\n 'model': board if board else nbr.get('platform', ''),\n 'version': nbr.get('version', '')\n }\n \n # 更新设备信息\n for dev in devices:\n mac = dev.get('mac', '')\n if mac in neighbor_map:\n info = neighbor_map[mac]\n dev['model'] = info.get('model', '')\n dev['version'] = info.get('version', '')\n if dev['model']:\n print(f\" ✅ {dev['ip']}: {dev['model']}\")\n except Exception as e:\n # API 不可用时,只显示 Unknown\n pass\n \n # 格式化结果\n print()\n lines.append(scanner.format_results())\n \n if not scanner.discovered_devices:\n lines.append(\"\\n💡 提示:\")\n lines.append(\" - 确保 MikroTik 设备启用了 MAC Telnet/Winbox 发现\")\n lines.append(\" - 命令:/tool mac-server winbox set enabled=yes\")\n lines.append(\" - 检查防火墙是否允许 UDP 5678 端口\")\n lines.append(\" - 确认 LattePanda 和 MikroTik 设备在同一网段\")\n \n return \"\\n\".join(lines)\n\n\ndef format_connections(api, quick):\n \"\"\"格式化活动连接统计\"\"\"\n lines = [\n \"🔗 活动连接\",\n \"=\" * 60,\n ]\n \n # 连接统计\n stats = quick.firewall.get_connection_stats()\n lines.append(f\"\\n📊 连接统计:\")\n lines.append(f\" 活跃连接数:{stats.get('total', 'N/A')}\")\n \n # 活动连接(前 20 条)\n connections = quick.firewall.get_active_connections(20)\n if connections:\n lines.append(f\"\\n🔥 TOP 连接:\")\n for i, conn in enumerate(connections[:20], 1):\n src = conn.get('src-address', 'N/A')\n dst = conn.get('dst-address', 'N/A')\n proto = conn.get('protocol', 'N/A')\n sport = conn.get('src-port', 'N/A')\n dport = conn.get('dst-port', 'N/A')\n lines.append(f\" {i}. {src}:{sport} → {dst}:{dport} ({proto})\")\n if len(connections) == 20:\n lines.append(f\" ... 还有更多连接\")\n \n return \"\\n\".join(lines)\n\n\ndef format_routing(api, quick):\n \"\"\"格式化路由配置\"\"\"\n lines = [\n \"🌐 路由配置\",\n \"=\" * 60,\n ]\n \n # 路由表统计\n routes = quick.routing.get_routes()\n static_routes = [r for r in routes if r.get('dynamic', '') != 'true']\n dynamic_routes = [r for r in routes if r.get('dynamic', '') == 'true']\n \n lines.append(f\"\\n📊 路由表统计:\")\n lines.append(f\" 总路由数:{len(routes)}\")\n lines.append(f\" 静态路由:{len(static_routes)}\")\n lines.append(f\" 动态路由:{len(dynamic_routes)}\")\n \n # 显示静态路由\n lines.append(f\"\\n🛤️ 静态路由:\")\n if static_routes:\n for i, route in enumerate(static_routes[:15], 1):\n dst = route.get('dst-address', 'N/A')\n gateway = route.get('gateway', 'N/A')\n distance = route.get('distance', '1')\n disabled = route.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} [{i}] {dst} via {gateway} (dist: {distance})\")\n if len(static_routes) > 15:\n lines.append(f\" ... 还有 {len(static_routes) - 15} 条\")\n else:\n lines.append(\" (无静态路由)\")\n \n # OSPF 状态\n ospf_instances = quick.routing.get_ospf_instances()\n if ospf_instances:\n lines.append(f\"\\n🔵 OSPF 配置 ({len(ospf_instances)} 实例):\")\n for inst in ospf_instances:\n name = inst.get('name', 'default')\n router_id = inst.get('router-id', 'N/A')\n lines.append(f\" - {name} (Router ID: {router_id})\")\n \n # OSPF 邻居\n ospf_neighbors = quick.routing.get_ospf_neighbors()\n if ospf_neighbors:\n lines.append(f\"\\n🔵 OSPF 邻居 ({len(ospf_neighbors)}):\")\n for nbr in ospf_neighbors[:10]:\n # 尝试多个字段名(不同 RouterOS 版本字段名可能不同)\n addr = nbr.get('neighbor-address') or nbr.get('address') or nbr.get('router-id') or 'N/A'\n state = nbr.get('state', 'N/A')\n interface = nbr.get('interface', 'N/A')\n state_icon = \"✅\" if state == 'Full' else \"⏳\"\n lines.append(f\" {state_icon} {addr} - {state} ({interface})\")\n if len(ospf_neighbors) > 10:\n lines.append(f\" ... 还有 {len(ospf_neighbors) - 10} 个邻居\")\n else:\n lines.append(\"\\n🔵 OSPF: 未配置\")\n \n # BGP 状态\n bgp_instances = quick.routing.get_bgp_instances()\n if bgp_instances:\n lines.append(f\"\\n🟠 BGP 配置 ({len(bgp_instances)} 实例):\")\n for inst in bgp_instances:\n name = inst.get('name', 'default')\n as_num = inst.get('as', 'N/A')\n router_id = inst.get('router-id', 'N/A')\n lines.append(f\" - {name} (AS: {as_num}, Router ID: {router_id})\")\n \n # BGP 对等体\n bgp_peers = quick.routing.get_bgp_peers()\n if bgp_peers:\n lines.append(f\"\\n🟠 BGP 对等体 ({len(bgp_peers)}):\")\n for peer in bgp_peers[:10]:\n name = peer.get('name', 'N/A')\n remote_addr = peer.get('remote-address', 'N/A')\n remote_as = peer.get('remote-as', 'N/A')\n disabled = peer.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} {name} - {remote_addr} (AS{remote_as})\")\n if len(bgp_peers) > 10:\n lines.append(f\" ... 还有 {len(bgp_peers) - 10} 个对等体\")\n else:\n lines.append(\"\\n🟠 BGP: 未配置\")\n \n return \"\\n\".join(lines)\n\n\ndef format_queues(api, quick):\n \"\"\"格式化队列/带宽限制配置\"\"\"\n lines = [\n \"📊 队列/带宽限制\",\n \"=\" * 60,\n ]\n \n # 简单队列\n lines.append(\"\\n🎯 简单队列:\")\n queues = quick.network.get_simple_queues()\n if queues:\n for i, queue in enumerate(queues, 1):\n name = queue.get('name', 'N/A')\n target = queue.get('target', 'N/A')\n max_limit = queue.get('max-limit', '0/0')\n limit_at = queue.get('limit-at', '0/0')\n disabled = queue.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n \n # 格式化带宽(转换为 Mbps)\n def format_bw(bw_str):\n try:\n down, up = bw_str.split('/')\n down_bps = int(down) if down.isdigit() else 0\n up_bps = int(up) if up.isdigit() else 0\n down_mbps = down_bps / 1000 / 1000\n up_mbps = up_bps / 1000 / 1000\n if down_mbps >= 1000:\n down_str = f\"{down_mbps/1000:.1f}G\"\n else:\n down_str = f\"{down_mbps:.0f}M\"\n if up_mbps >= 1000:\n up_str = f\"{up_mbps/1000:.1f}G\"\n else:\n up_str = f\"{up_mbps:.0f}M\"\n return f\"{down_str}/{up_str}\"\n except:\n return bw_str\n \n bw_display = format_bw(max_limit)\n lines.append(f\" {status} [{i}] {name}\")\n lines.append(f\" 目标:{target}\")\n lines.append(f\" 限制:{bw_display} (下载/上传)\")\n if limit_at != '0/0':\n lines.append(f\" 保证:{format_bw(limit_at)}\")\n else:\n lines.append(\" (无简单队列)\")\n \n # 队列树\n queue_tree = quick.network.get_queue_tree()\n if queue_tree:\n lines.append(f\"\\n🌳 队列树 ({len(queue_tree)} 条):\")\n for i, qt in enumerate(queue_tree[:10], 1):\n name = qt.get('name', 'N/A')\n parent = qt.get('parent', 'N/A')\n packet_marks = qt.get('packet-marks', 'N/A')\n max_limit = qt.get('max-limit', '0')\n disabled = qt.get('disabled', '') == 'true'\n status = \"⏸️\" if disabled else \"✅\"\n lines.append(f\" {status} [{i}] {name} (parent: {parent})\")\n if len(queue_tree) > 10:\n lines.append(f\" ... 还有 {len(queue_tree) - 10} 条\")\n \n # 队列类型\n queue_types = quick.network.get_queue_types()\n if queue_types:\n lines.append(f\"\\n📦 队列类型 ({len(queue_types)}):\")\n for qt in queue_types[:5]:\n name = qt.get('name', 'N/A')\n kind = qt.get('kind', 'N/A')\n lines.append(f\" - {name} ({kind})\")\n if len(queue_types) > 5:\n lines.append(f\" ... 还有 {len(queue_types) - 5} 种\")\n \n return \"\\n\".join(lines)\n\n\ndef execute_command(device, command):\n \"\"\"\n 执行 MikroTik 命令\n \n 支持空密码和有密码两种情况\n \"\"\"\n config = get_device_config(device)\n \n if not config:\n # 提供友好的错误提示\n error_msg = \"❌ 未找到设备配置\\n\\n\"\n error_msg += \"请在 TOOLS.md 中添加 MikroTik 设备配置:\\n\"\n error_msg += \"```markdown\\n\"\n error_msg += \"### MikroTik 设备\\n\"\n error_msg += \"- **office**: 192.168.1.1, admin, 空密码\\n\"\n error_msg += \"- **home**:192.168.88.1, admin, yourpassword\\n\"\n error_msg += \"```\\n\"\n error_msg += \"\\n或使用环境变量:\\n\"\n error_msg += \"- `MIKROTIK_HOST`: 设备 IP\\n\"\n error_msg += \"- `MIKROTIK_USER`: 用户名 (可选,默认 admin)\\n\"\n error_msg += \"- `MIKROTIK_PASS`: 密码 (可选,支持空密码)\"\n return error_msg\n \n try:\n # 显示连接信息(调试用,生产环境可移除)\n pwd_display = \"(空密码)\" if config['password'] == '' else \"(已配置密码)\"\n print(f\"🔌 连接设备:{config['host']} [{config['username']}] {pwd_display}\")\n \n api = MikroTikAPI(\n config['host'], \n config['username'], \n config['password'],\n timeout=10 # 增加超时时间\n )\n \n if not api.connect():\n return f\"❌ 无法连接到 {config['host']}\\n\\n请检查:\\n1. 设备 IP 是否正确\\n2. 网络是否可达\\n3. API 服务是否启用(默认端口 8728)\"\n \n if not api.login():\n pwd_hint = \"空密码\" if config['password'] == '' else \"密码可能错误\"\n return f\"❌ 登录失败 ({pwd_hint})\\n\\n请检查:\\n1. 用户名/密码是否正确\\n2. 用户是否有 API 访问权限\"\n \n quick = QuickCommands(api)\n \n # 根据命令类型返回不同格式\n if 'status' in command.lower() or '状态' in command:\n result = format_status(api, quick)\n elif 'firewall' in command.lower() or '防火墙' in command:\n result = format_firewall(api, quick)\n elif 'interface' in command.lower() or '接口' in command:\n # 检查是否要查看详细信息\n if 'detail' in command.lower() or '详细' in command:\n # 提取接口名\n import re\n match = re.search(r'(ether\\d+|bridge|vlan\\d+|wireguard\\w+|cap\\d+|lo)', command)\n iface_name = match.group(1) if match else ''\n result = format_interface_detail(api, quick, iface_name)\n else:\n result = format_interfaces(api, quick)\n elif 'traffic' in command.lower() or '流量' in command:\n result = format_traffic(api, quick)\n elif 'dhcp' in command.lower():\n result = format_dhcp(api, quick)\n elif 'arp' in command.lower():\n result = format_arp(api, quick)\n elif 'client' in command.lower() or '客户端' in command or '无线' in command or 'wifi' in command or 'wi-fi' in command:\n result = format_clients(api, quick)\n elif 'wireguard' in command.lower() or 'wg' in command.lower():\n result = format_wireguard(api, quick)\n elif 'user' in command.lower() or '用户' in command:\n result = format_users(api, quick)\n elif 'log' in command.lower() or '日志' in command:\n result = format_logs(api, quick)\n elif 'service' in command.lower() or '服务' in command:\n result = format_services(api, quick)\n elif 'vlan' in command.lower():\n result = format_vlan(api, quick)\n elif 'bridge' in command.lower() or '桥接' in command:\n result = format_bridge(api, quick)\n elif 'queue' in command.lower() or '队列' in command or '带宽' in command or '限速' in command:\n result = format_queues(api, quick)\n elif 'route' in command.lower() or 'routing' in command.lower() or '路由' in command.lower() or 'ospf' in command.lower() or 'bgp' in command.lower():\n result = format_routing(api, quick)\n elif 'health' in command.lower() or '温度' in command.lower() or '电压' in command.lower() or '风扇' in command.lower() or '硬件' in command.lower():\n result = format_health(api, quick)\n elif 'scheduler' in command.lower() or '计划' in command.lower() or '定时' in command.lower() or '任务' in command.lower():\n result = format_scheduler(api, quick)\n elif 'neighbor' in command.lower() or '邻居' in command.lower() or '设备发现' in command.lower():\n result = format_neighbors(api, quick)\n elif 'connection' in command.lower() or '连接' in command.lower() or '活动连接' in command.lower():\n result = format_connections(api, quick)\n elif 'ping' in command.lower():\n # 提取目标地址\n import re\n match = re.search(r'(\\d+\\.\\d+\\.\\d+\\.\\d+|[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})', command)\n target = match.group(1) if match else '8.8.8.8'\n results = api.run_command('/ping', [f'=address={target}', '=count=5'])\n if results:\n result = f\"🏓 Ping {target}:\\n\"\n for r in results:\n if 'sent' in r:\n result += f\" 发送:{r.get('sent')}, 接收:{r.get('received')}, 丢失:{r.get('lost')}\"\n elif 'status' in r:\n result += f\" 状态:{r.get('status')}\"\n else:\n result = \"(无结果)\"\n elif 'scan' in command.lower() or '扫描' in command.lower():\n # 扫描功能独立工作,不依赖设备配置\n result = format_scan(api, quick)\n else:\n # 执行自定义命令\n results = api.run_command(command)\n if results:\n result = \"命令执行结果:\\n\"\n for item in results:\n for key, value in item.items():\n result += f\" {key}: {value}\\n\"\n else:\n result = \"(无结果)\"\n \n api.disconnect()\n return result\n \n except ConnectionRefusedError:\n return f\"❌ 连接被拒绝:{config['host']}\\n\\n可能原因:\\n1. API 服务未启用\\n2. 防火墙阻止了 8728 端口\\n3. 设备离线\"\n except TimeoutError:\n return f\"❌ 连接超时:{config['host']}\\n\\n请检查网络连通性\"\n except Exception as e:\n return f\"❌ 错误:{type(e).__name__}: {e}\"\n\n\ndef handle_message(message):\n \"\"\"处理用户消息\"\"\"\n original_message = message\n message = message.lower().strip()\n \n # 解析命令\n if 'mikrotik' in message or 'routeros' in message or '路由器' in message:\n # 提取设备名称(支持中文和英文)\n device = None\n \n # 尝试从 TOOLS.md 中匹配已配置的设备名称\n tools_md_path = os.path.expanduser('~/.openclaw/workspace/TOOLS.md')\n if os.path.exists(tools_md_path):\n try:\n with open(tools_md_path, 'r', encoding='utf-8') as f:\n content = f.read()\n \n # 提取所有已配置的设备名称\n mikrotik_section = re.search(r'###\\s*MikroTik 设备.*?\\n(.*?)(?=\\n###|\\Z)', content, re.DOTALL | re.IGNORECASE)\n if mikrotik_section:\n section_text = mikrotik_section.group(1)\n device_names = re.findall(r'\\*\\*([^*]+)\\*\\*', section_text)\n \n # 检查消息中是否包含已配置的设备名称\n for name in device_names:\n if name.strip().lower() in message or name.strip() in original_message:\n device = name.strip().lower()\n break\n except:\n pass\n \n # 扫描命令独立处理,不依赖设备配置\n if '扫描' in message or 'scan' in message:\n return format_scan(None, None)\n \n # 如果没有匹配到设备名称,使用默认值\n if not device:\n device = 'default' # 使用默认设备\n \n # 检查命令类型\n if '状态' in message or 'status' in message:\n return execute_command(device, 'status')\n elif '防火墙' in message or 'firewall' in message:\n return execute_command(device, 'firewall')\n elif '接口' in message or 'interface' in message:\n return execute_command(device, 'interfaces')\n elif '客户端' in message or '无线' in message or 'wifi' in message or 'wi-fi' in message:\n return execute_command(device, 'clients')\n elif '执行' in message or '命令' in message:\n # 提取命令路径\n if '/ip/' in message or '/system/' in message:\n cmd_start = message.find('/')\n command = message[cmd_start:].split()[0]\n return execute_command(device, command)\n \n return None # 不是本 skill 处理的命令\n\n\nif __name__ == '__main__':\n # 测试\n if len(sys.argv) > 1:\n device = sys.argv[1] if len(sys.argv) > 1 else 'office'\n command = sys.argv[2] if len(sys.argv) > 2 else 'status'\n print(execute_command(device, command))\n else:\n print(\"用法:python handler.py [设备] [命令]\")\n print(\"示例:python handler.py office status\")\n","content_type":"text/x-python; charset=utf-8","language":"python","size":46740,"content_sha256":"943c6a2886e4ea699074eeac143a727ac7f86834829630b8d1b62e991574fdc6"},{"filename":"mikrotik-api/__init__.py","content":"#!/usr/bin/env python3\n\"\"\"\nMikroTik RouterOS API Client\n基于官方文档:https://help.mikrotik.com/docs/display/ROS/API\n\n用法:\n from mikrotik_api import MikroTikAPI\n \n api = MikroTikAPI('192.168.1.1', username='admin', password='')\n api.connect()\n \n # 获取系统信息\n info = api.get_system_resource()\n print(info)\n \n # 执行自定义命令\n interfaces = api.run_command('/interface/print')\n print(interfaces)\n \n api.disconnect()\n\"\"\"\n\nfrom .client import MikroTikAPI\nfrom .commands import SystemCommands, FirewallCommands, NetworkCommands\n\n__version__ = '1.0.0'\n__author__ = '虾哥'\n\n__all__ = ['MikroTikAPI', 'SystemCommands', 'FirewallCommands', 'NetworkCommands']\n","content_type":"text/x-python; charset=utf-8","language":"python","size":720,"content_sha256":"609facb88d504406df1e973bc91501e7b6b5d4382a14a0adfa568e703551e4da"},{"filename":"mikrotik-api/cli.py","content":"#!/usr/bin/env python3\n\"\"\"\nMikroTik RouterOS API 命令行工具\n\n用法:\n python cli.py \u003chost> [command] [options]\n\n示例:\n python cli.py 192.168.1.1 status # 查看设备状态\n python cli.py 192.168.1.1 firewall # 查看防火墙\n python cli.py 192.168.1.1 interfaces # 查看接口\n python cli.py 192.168.1.1 cmd /ip/address/print # 执行自定义命令\n\"\"\"\n\nimport sys\nimport argparse\n\n# 支持直接运行和包导入两种模式\ntry:\n from .client import MikroTikAPI\n from .commands import QuickCommands\nexcept ImportError:\n from client import MikroTikAPI\n from commands import QuickCommands\n\n\ndef main():\n parser = argparse.ArgumentParser(description='MikroTik RouterOS API 命令行工具')\n parser.add_argument('host', help='RouterOS 设备 IP 地址')\n parser.add_argument('-u', '--username', default='admin', help='用户名 (默认:admin)')\n parser.add_argument('-p', '--password', default='', help='密码 (默认:空)')\n parser.add_argument('--port', type=int, default=8728, help='API 端口 (默认:8728)')\n parser.add_argument('command', nargs='?', default='status', \n choices=['status', 'firewall', 'interfaces', 'routes', 'cmd'],\n help='要执行的命令')\n parser.add_argument('args', nargs='*', help='命令参数')\n \n args = parser.parse_args()\n \n # 连接设备\n api = MikroTikAPI(args.host, args.username, args.password, args.port)\n \n if not api.connect():\n print(f\"❌ 无法连接到 {args.host}:{args.port}\")\n sys.exit(1)\n \n if not api.login():\n print(\"❌ 登录失败\")\n api.disconnect()\n sys.exit(1)\n \n quick = QuickCommands(api)\n \n try:\n if args.command == 'status':\n quick.print_status()\n \n elif args.command == 'firewall':\n print(\"=\" * 60)\n print(\"🔥 防火墙规则\")\n print(\"=\" * 60)\n \n # 过滤规则\n print(\"\\n📋 过滤规则:\")\n rules = quick.firewall.get_filter_rules()\n if rules:\n for i, rule in enumerate(rules, 1):\n chain = rule.get('chain', 'N/A')\n action = rule.get('action', 'N/A')\n disabled = rule.get('disabled', '') == 'true'\n comment = rule.get('comment', '')\n status = \"⏸️\" if disabled else \"✅\"\n print(f\" {status} [{i}] {chain}: {action}\" + \n (f\" ({comment})\" if comment else \"\"))\n else:\n print(\" (无规则)\")\n \n # NAT 规则\n print(\"\\n🔄 NAT 规则:\")\n rules = quick.firewall.get_nat_rules()\n if rules:\n for i, rule in enumerate(rules, 1):\n chain = rule.get('chain', 'N/A')\n action = rule.get('action', 'N/A')\n to_addr = rule.get('to-addresses', '')\n comment = rule.get('comment', '')\n print(f\" [{i}] {chain}: {action}\" + \n (f\" → {to_addr}\" if to_addr else \"\") +\n (f\" ({comment})\" if comment else \"\"))\n else:\n print(\" (无规则)\")\n \n print(\"=\" * 60)\n \n elif args.command == 'interfaces':\n print(\"=\" * 60)\n print(\"🔌 网络接口\")\n print(\"=\" * 60)\n interfaces = quick.network.get_interfaces()\n for iface in interfaces:\n name = iface.get('name', 'unknown')\n running = iface.get('running', 'false') == 'true'\n mtu = iface.get('mtu', 'N/A')\n mac = iface.get('mac-address', 'N/A')\n status = \"✅\" if running else \"❌\"\n print(f\" {status} {name} (MTU: {mtu}, MAC: {mac})\")\n print(\"=\" * 60)\n \n elif args.command == 'routes':\n print(\"=\" * 60)\n print(\"🛤️ 路由表\")\n print(\"=\" * 60)\n routes = quick.network.get_routes()\n for route in routes:\n dst = route.get('dst-address', 'N/A')\n gateway = route.get('gateway', 'N/A')\n distance = route.get('distance', '1')\n print(f\" {dst} via {gateway} (distance: {distance})\")\n print(\"=\" * 60)\n \n elif args.command == 'cmd':\n if not args.args:\n print(\"❌ 请指定命令路径,如:/system/resource/print\")\n sys.exit(1)\n \n cmd = args.args[0]\n results = api.run_command(cmd)\n \n if results:\n for item in results:\n for key, value in item.items():\n print(f\" {key}: {value}\")\n print()\n else:\n print(\"(无结果)\")\n \n finally:\n api.disconnect()\n\n\nif __name__ == '__main__':\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":5092,"content_sha256":"474d08cfad3daea119fd8677428f9943a0e48465df6267008c819ee8a60831f3"},{"filename":"mikrotik-api/client.py","content":"#!/usr/bin/env python3\n\"\"\"\nMikroTik RouterOS API Client - 核心连接模块\n\"\"\"\n\nimport socket\nimport select\nfrom typing import List, Dict, Optional, Any\n\n\nclass MikroTikAPI:\n \"\"\"MikroTik RouterOS API 客户端\"\"\"\n \n def __init__(self, host: str, username: str = 'admin', password: str = '', \n port: int = 8728, timeout: int = 5):\n \"\"\"\n 初始化 API 客户端\n \n Args:\n host: RouterOS 设备 IP 地址\n username: 用户名 (默认:admin)\n password: 密码 (默认:空)\n port: API 端口 (默认:8728, SSL 用 8729)\n timeout: 连接超时 (秒)\n \"\"\"\n self.host = host\n self.port = port\n self.username = username\n self.password = password\n self.timeout = timeout\n self.sock: Optional[socket.socket] = None\n self.connected = False\n \n def connect(self) -> bool:\n \"\"\"建立连接到 RouterOS\"\"\"\n try:\n self.sock = socket.create_connection((self.host, self.port), timeout=self.timeout)\n self.sock.setblocking(0) # 非阻塞模式(select 处理超时)\n self.connected = True\n return True\n except Exception as e:\n print(f\"❌ 连接失败:{e}\")\n return False\n \n def disconnect(self):\n \"\"\"断开连接\"\"\"\n if self.sock:\n try:\n self.sock.close()\n except:\n pass\n self.connected = False\n \n def _send_word(self, word: str) -> None:\n \"\"\"发送一个 API 命令字\"\"\"\n if not self.sock:\n raise ConnectionError(\"未连接到 RouterOS\")\n \n data = word.encode('utf-8')\n length = len(data)\n \n # 编码长度前缀\n if length \u003c 0x80:\n header = bytes([length])\n elif length \u003c 0x4000:\n header = bytes([0x80 | (length >> 8), length & 0xFF])\n elif length \u003c 0x200000:\n header = bytes([0xC0 | (length >> 16), (length >> 8) & 0xFF, length & 0xFF])\n elif length \u003c 0x10000000:\n header = bytes([0xE0 | (length >> 24), (length >> 16) & 0xFF, \n (length >> 8) & 0xFF, length & 0xFF])\n else:\n header = bytes([0xF0, (length >> 24) & 0xFF, (length >> 16) & 0xFF,\n (length >> 8) & 0xFF, length & 0xFF])\n \n self.sock.sendall(header + data)\n \n def _recv_word(self, timeout: float = 5.0) -> Optional[str]:\n \"\"\"\n 接收一个 API 词(word)\n \n RouterOS API 使用 length-prefixed 格式:\n - 第 1 位:长度标记\n - 后续字节:实际长度(根据标记位计算)\n - 最后:实际数据\n \n Returns:\n 解码后的字符串,或 None(超时/错误)\n \"\"\"\n if not self.sock:\n return None\n \n try:\n # 读取长度前缀(第 1 字节)\n self.sock.setblocking(0)\n ready = select.select([self.sock], [], [], timeout)\n if not ready[0]:\n return None\n \n first_byte = self.sock.recv(1)\n if not first_byte:\n return None\n \n length = first_byte[0]\n \n # 根据第 1 位计算实际长度\n if length & 0x80:\n if length & 0x40:\n if length & 0x20:\n if length & 0x10:\n # 5 字节:11110xxx + 4 字节长度\n length_bytes = self.sock.recv(4)\n if len(length_bytes) \u003c 4:\n return None\n length = ((length & 0x0F) \u003c\u003c 24) | int.from_bytes(length_bytes, 'big')\n else:\n # 4 字节:1110xxxx + 3 字节长度\n length_bytes = self.sock.recv(3)\n if len(length_bytes) \u003c 3:\n return None\n length = ((length & 0x07) \u003c\u003c 16) | int.from_bytes(length_bytes, 'big')\n else:\n # 3 字节:110xxxxx + 2 字节长度\n length_bytes = self.sock.recv(2)\n if len(length_bytes) \u003c 2:\n return None\n length = ((length & 0x1F) \u003c\u003c 8) | length_bytes[1]\n else:\n # 2 字节:10xxxxxx + 1 字节长度\n second_byte = self.sock.recv(1)\n if not second_byte:\n return None\n length = ((length & 0x3F) \u003c\u003c 8) | second_byte[0]\n # else: 长度 \u003c 0x80,直接使用\n \n # 读取实际数据\n if length > 0:\n data = b''\n while len(data) \u003c length:\n ready = select.select([self.sock], [], [], timeout)\n if not ready[0]:\n break\n chunk = self.sock.recv(length - len(data))\n if not chunk:\n break\n data += chunk\n \n return data.decode('utf-8', errors='ignore')\n \n return ''\n \n except Exception as e:\n print(f\"⚠️ 接收词失败:{e}\")\n return None\n \n def _recv_response(self, timeout: float = 5.0) -> List[Dict[str, Any]]:\n \"\"\"\n 接收完整的 API 响应\n \n RouterOS API 响应格式:\n - !re (记录/条目)\n - !done (完成)\n - !trap (错误)\n - !halt (停止)\n \n 每个消息后跟多个 =key=value,以空字符串结束\n \n Returns:\n 解析后的条目列表\n \"\"\"\n results = []\n current_entry = {}\n message_type = None\n \n try:\n while True:\n word = self._recv_word(timeout)\n \n if word is None:\n # 超时,返回已接收的数据\n break\n \n if word == '':\n # 空词表示当前消息结束\n if message_type == '!re' and current_entry:\n results.append(current_entry)\n current_entry = {}\n elif message_type in ('!done', '!trap', '!halt'):\n # 完成/错误消息,返回结果\n break\n elif word.startswith('!'):\n # 消息类型\n if message_type == '!re' and current_entry:\n results.append(current_entry)\n current_entry = {}\n message_type = word\n elif word.startswith('='):\n # =key=value 格式\n if '=' in word[1:]:\n key, value = word[1:].split('=', 1)\n current_entry[key] = value\n \n # 减小后续超时\n timeout = 0.5\n \n except Exception as e:\n print(f\"⚠️ 接收响应失败:{e}\")\n \n return results\n \n def _parse_response(self, data: bytes) -> List[Dict[str, str]]:\n \"\"\"\n 解析 API 响应数据(向后兼容,已废弃)\n \n 请使用 _recv_response() 代替\n \"\"\"\n return self._recv_response()\n \n def login(self) -> bool:\n \"\"\"登录到 RouterOS\"\"\"\n if not self.sock:\n return False\n \n try:\n # 发送登录命令\n self._send_word('/login')\n self._send_word(f'=name={self.username}')\n self._send_word(f'=password={self.password}')\n self._send_word('') # 结束标记\n \n # 接收响应(使用新的解析器)\n response = self._recv_response(timeout=3.0)\n # 检查是否有 !done 响应\n return len(response) > 0 or True # 只要没有错误就认为成功\n except Exception as e:\n print(f\"❌ 登录失败:{e}\")\n return False\n \n def run_command(self, command: str, args: Optional[List[str]] = None) -> List[Dict[str, str]]:\n \"\"\"\n 执行 API 命令\n \n Args:\n command: 命令路径 (如 '/system/resource/print')\n args: 额外参数列表 (如 ['=detail=', '=active='])\n \n Returns:\n 解析后的响应数据列表(支持多条目)\n \"\"\"\n if not self.connected:\n raise ConnectionError(\"未连接到 RouterOS\")\n \n try:\n # 发送命令\n self._send_word(command)\n if args:\n for arg in args:\n self._send_word(arg)\n self._send_word('') # 结束标记\n \n # 接收并解析响应(使用新的解析器)\n return self._recv_response(timeout=5.0)\n except Exception as e:\n print(f\"❌ 命令执行失败:{e}\")\n return []\n \n def __enter__(self):\n \"\"\"上下文管理器入口\"\"\"\n self.connect()\n return self\n \n def __exit__(self, exc_type, exc_val, exc_tb):\n \"\"\"上下文管理器出口\"\"\"\n self.disconnect()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9571,"content_sha256":"c77b0429c2fb74d3cbda881dfa3592c6a6c3bd78d0d973eaa7baa654e7c347db"},{"filename":"mikrotik-api/commands.py","content":"#!/usr/bin/env python3\n\"\"\"\nMikroTik RouterOS API - 常用命令封装\n\"\"\"\n\nfrom typing import List, Dict, Optional\n\n# 支持直接运行和包导入两种模式\ntry:\n from .client import MikroTikAPI\nexcept ImportError:\n from client import MikroTikAPI\n\n\nclass SystemCommands:\n \"\"\"系统相关命令\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n \n def get_resource(self) -> Dict:\n \"\"\"获取系统资源信息\"\"\"\n results = self.api.run_command('/system/resource/print')\n return results[0] if results else {}\n \n def get_identity(self) -> Dict:\n \"\"\"获取设备标识\"\"\"\n results = self.api.run_command('/system/identity/print')\n return results[0] if results else {}\n \n def get_version(self) -> Dict:\n \"\"\"获取系统版本\"\"\"\n results = self.api.run_command('/system/routerboard/print')\n return results[0] if results else {}\n \n def get_health(self) -> Dict:\n \"\"\"获取健康状态(温度、电压等)\"\"\"\n results = self.api.run_command('/system/health/print')\n return results[0] if results else {}\n \n def get_uptime(self) -> str:\n \"\"\"获取运行时间\"\"\"\n resource = self.get_resource()\n return resource.get('uptime', 'N/A')\n \n def get_users(self) -> List[Dict]:\n \"\"\"获取系统用户列表\"\"\"\n return self.api.run_command('/user/print')\n \n def get_services(self) -> List[Dict]:\n \"\"\"获取系统服务(API、SSH、WWW 等)\"\"\"\n return self.api.run_command('/ip/service/print')\n \n def get_scheduler(self) -> List[Dict]:\n \"\"\"获取定时任务列表\"\"\"\n return self.api.run_command('/system/scheduler/print')\n \n def get_scripts(self) -> List[Dict]:\n \"\"\"获取脚本列表\"\"\"\n return self.api.run_command('/system/script/print')\n \n def get_logging(self) -> List[Dict]:\n \"\"\"获取日志配置\"\"\"\n return self.api.run_command('/system/logging/print')\n \n def get_scheduler(self) -> List[Dict]:\n \"\"\"获取定时任务列表\"\"\"\n return self.api.run_command('/system/scheduler/print')\n \n def get_watchdog(self) -> Dict:\n \"\"\"获取看门狗配置\"\"\"\n results = self.api.run_command('/system/watchdog/print')\n return results[0] if results else {}\n \n def get_recent_logs(self, count: int = 20) -> List[Dict]:\n \"\"\"\n 获取最近日志\n \n Args:\n count: 日志条数\n \"\"\"\n return self.api.run_command('/log/print', [f'=.count={count}'])\n \n def reboot(self):\n \"\"\"重启设备\"\"\"\n self.api.run_command('/system/reboot')\n \n def shutdown(self):\n \"\"\"关闭设备\"\"\"\n self.api.run_command('/system/shutdown')\n\n\nclass FirewallCommands:\n \"\"\"防火墙相关命令\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n \n def get_filter_rules(self) -> List[Dict]:\n \"\"\"获取过滤规则\"\"\"\n return self.api.run_command('/ip/firewall/filter/print')\n \n def get_nat_rules(self) -> List[Dict]:\n \"\"\"获取 NAT 规则\"\"\"\n return self.api.run_command('/ip/firewall/nat/print')\n \n def get_mangle_rules(self) -> List[Dict]:\n \"\"\"获取 Mangle 规则\"\"\"\n return self.api.run_command('/ip/firewall/mangle/print')\n \n def get_address_lists(self) -> List[Dict]:\n \"\"\"获取地址列表\"\"\"\n return self.api.run_command('/ip/firewall/address-list/print')\n \n def get_active_connections(self, count: int = 100) -> List[Dict]:\n \"\"\"获取活动连接\"\"\"\n return self.api.run_command('/ip/firewall/active/print', \n [f'=.proplist=src-address,dst-address,protocol,src-port,dst-port'])\n \n def get_connection_stats(self) -> Dict:\n \"\"\"获取连接统计\"\"\"\n results = self.api.run_command('/ip/firewall/connection/print', ['=count-only='])\n return {'total': len(results)} if results else {}\n \n def get_raw_rules(self) -> List[Dict]:\n \"\"\"获取 Raw 规则(预处理防火墙)\"\"\"\n return self.api.run_command('/ip/firewall/raw/print')\n \n def get_connection_tracking(self) -> Dict:\n \"\"\"获取连接跟踪状态\"\"\"\n results = self.api.run_command('/ip/firewall/connection/print', ['=count-only='])\n return {'active_connections': len(results)} if results else {}\n\n\nclass NetworkCommands:\n \"\"\"网络相关命令\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n \n def get_interfaces(self) -> List[Dict]:\n \"\"\"获取网络接口列表\"\"\"\n return self.api.run_command('/interface/print')\n \n def get_ip_addresses(self) -> List[Dict]:\n \"\"\"获取 IP 地址配置\"\"\"\n return self.api.run_command('/ip/address/print')\n \n def get_routes(self) -> List[Dict]:\n \"\"\"获取路由表\"\"\"\n return self.api.run_command('/ip/route/print')\n \n def get_dns(self) -> List[Dict]:\n \"\"\"获取 DNS 配置\"\"\"\n return self.api.run_command('/ip/dns/print')\n \n def get_dhcp_leases(self) -> List[Dict]:\n \"\"\"获取 DHCP 租约\"\"\"\n return self.api.run_command('/ip/dhcp-server/lease/print')\n \n def get_dhcp_servers(self) -> List[Dict]:\n \"\"\"获取 DHCP 服务器配置\"\"\"\n return self.api.run_command('/ip/dhcp-server/print')\n \n def get_arp(self) -> List[Dict]:\n \"\"\"获取 ARP 表\"\"\"\n return self.api.run_command('/ip/arp/print')\n \n def get_neighbors(self) -> List[Dict]:\n \"\"\"获取邻居发现\"\"\"\n return self.api.run_command('/ip/neighbor/print')\n \n def get_wireguard_peers(self) -> List[Dict]:\n \"\"\"获取 WireGuard 对等体状态\"\"\"\n return self.api.run_command('/interface/wireguard/peer/print')\n \n def get_vlan_interfaces(self) -> List[Dict]:\n \"\"\"获取 VLAN 接口列表\"\"\"\n return self.api.run_command('/interface/vlan/print')\n \n def get_bridge_ports(self) -> List[Dict]:\n \"\"\"获取桥接端口列表\"\"\"\n return self.api.run_command('/interface/bridge/port/print')\n \n def get_traffic_stats(self, interface: str = '') -> List[Dict]:\n \"\"\"\n 获取接口流量统计\n \n Args:\n interface: 指定接口名,空则返回所有接口\n \"\"\"\n if interface:\n return self.api.run_command('/interface/print', [f'=.where=name={interface}'])\n return self.api.run_command('/interface/print')\n \n def get_interface_stats(self, interface: str = '') -> List[Dict]:\n \"\"\"\n 获取接口详细统计(字节数、包数)\n \n Args:\n interface: 指定接口名,空则返回所有接口\n \"\"\"\n # 获取所有接口统计,然后在 Python 层面过滤\n all_stats = self.api.run_command('/interface/print')\n \n if interface:\n # 过滤指定接口\n return [iface for iface in all_stats if iface.get('name') == interface]\n return all_stats\n \n def get_wireguard_status(self) -> List[Dict]:\n \"\"\"获取 WireGuard 接口状态\"\"\"\n return self.api.run_command('/interface/wireguard/print')\n \n def get_bridge(self) -> List[Dict]:\n \"\"\"获取桥接配置\"\"\"\n return self.api.run_command('/interface/bridge/print')\n \n def get_vlan(self) -> List[Dict]:\n \"\"\"获取 VLAN 配置\"\"\"\n return self.api.run_command('/interface/vlan/print')\n \n def get_pppoe(self) -> List[Dict]:\n \"\"\"获取 PPPoE 客户端配置\"\"\"\n return self.api.run_command('/interface/pppoe-client/print')\n \n def get_bonding(self) -> List[Dict]:\n \"\"\"获取链路聚合(Bonding)配置\"\"\"\n return self.api.run_command('/interface/bonding/print')\n \n def get_simple_queues(self) -> List[Dict]:\n \"\"\"获取简单队列(带宽限制)配置\"\"\"\n return self.api.run_command('/queue/simple/print')\n \n def get_queue_tree(self) -> List[Dict]:\n \"\"\"获取队列树配置\"\"\"\n return self.api.run_command('/queue/tree/print')\n \n def get_queue_types(self) -> List[Dict]:\n \"\"\"获取队列类型定义\"\"\"\n return self.api.run_command('/queue/type/print')\n\n\nclass RoutingCommands:\n \"\"\"路由相关命令\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n \n def get_routes(self) -> List[Dict]:\n \"\"\"获取路由表\"\"\"\n return self.api.run_command('/ip/route/print')\n \n def get_static_routes(self) -> List[Dict]:\n \"\"\"获取静态路由\"\"\"\n return self.api.run_command('/ip/route/print', ['=.where=dynamic=false'])\n \n def get_dynamic_routes(self) -> List[Dict]:\n \"\"\"获取动态路由\"\"\"\n return self.api.run_command('/ip/route/print', ['=.where=dynamic=true'])\n \n def get_ospf_instances(self) -> List[Dict]:\n \"\"\"获取 OSPF 实例配置\"\"\"\n return self.api.run_command('/routing/ospf/instance/print')\n \n def get_ospf_areas(self) -> List[Dict]:\n \"\"\"获取 OSPF 区域配置\"\"\"\n return self.api.run_command('/routing/ospf/area/print')\n \n def get_ospf_interfaces(self) -> List[Dict]:\n \"\"\"获取 OSPF 接口配置\"\"\"\n return self.api.run_command('/routing/ospf/interface/print')\n \n def get_ospf_neighbors(self) -> List[Dict]:\n \"\"\"获取 OSPF 邻居状态\"\"\"\n return self.api.run_command('/routing/ospf/neighbor/print')\n \n def get_ospf_lsdb(self) -> List[Dict]:\n \"\"\"获取 OSPF 链路状态数据库\"\"\"\n return self.api.run_command('/routing/ospf/lsdb/print')\n \n def get_bgp_instances(self) -> List[Dict]:\n \"\"\"获取 BGP 实例配置\"\"\"\n return self.api.run_command('/routing/bgp/instance/print')\n \n def get_bgp_peers(self) -> List[Dict]:\n \"\"\"获取 BGP 对等体配置\"\"\"\n return self.api.run_command('/routing/bgp/peer/print')\n \n def get_bgp_sessions(self) -> List[Dict]:\n \"\"\"获取 BGP 会话状态\"\"\"\n return self.api.run_command('/routing/bgp/session/print')\n \n def get_bgp_routes(self) -> List[Dict]:\n \"\"\"获取 BGP 路由\"\"\"\n return self.api.run_command('/routing/bgp/advertised-route/print')\n \n def get_bgp_networks(self) -> List[Dict]:\n \"\"\"获取 BGP 网络宣告\"\"\"\n return self.api.run_command('/routing/bgp/network/print')\n \n def get_mpls_interfaces(self) -> List[Dict]:\n \"\"\"获取 MPLS 接口配置\"\"\"\n return self.api.run_command('/mpls/interface/print')\n \n def get_mpls_ldp_neighbors(self) -> List[Dict]:\n \"\"\"获取 MPLS LDP 邻居\"\"\"\n return self.api.run_command('/mpls/ldp/neighbor/print')\n \n def get_ping(self, host: str, count: int = 5) -> List[Dict]:\n \"\"\"\n 执行 Ping 测试\n \n Args:\n host: 目标地址\n count: Ping 次数\n \"\"\"\n return self.api.run_command('/ping', [f'=address={host}', f'=count={count}'])\n \n def get_traceroute(self, host: str) -> List[Dict]:\n \"\"\"\n 执行 Traceroute\n \n Args:\n host: 目标地址\n \"\"\"\n return self.api.run_command('/tool/traceroute', [f'=address={host}'])\n \n def get_dns_cache(self) -> List[Dict]:\n \"\"\"获取 DNS 缓存\"\"\"\n return self.api.run_command('/ip/dns/cache/print')\n \n def get_bandwidth_test(self, interface: str = '') -> List[Dict]:\n \"\"\"\n 带宽测试(需要两端支持)\n \n Args:\n interface: 指定接口\n \"\"\"\n if interface:\n return self.api.run_command('/tool/bandwidth-test', [f'=interface={interface}'])\n return self.api.run_command('/tool/bandwidth-server/print')\n \n def get_poe(self) -> List[Dict]:\n \"\"\"获取 PoE 状态\"\"\"\n return self.api.run_command('/interface/ethernet/poe/print')\n \n def get_usb(self) -> List[Dict]:\n \"\"\"获取 USB 设备列表\"\"\"\n return self.api.run_command('/system/usb/print')\n \n def get_storage(self) -> List[Dict]:\n \"\"\"获取存储设备信息\"\"\"\n return self.api.run_command('/disk/print')\n\n\nclass UserCommands:\n \"\"\"用户和 PPP 相关命令\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n \n def get_ppp_users(self) -> List[Dict]:\n \"\"\"获取 PPP 用户(PPPoE/PPTP/L2TP)\"\"\"\n return self.api.run_command('/ppp/secret/print')\n \n def get_ppp_active(self) -> List[Dict]:\n \"\"\"获取活跃 PPP 连接\"\"\"\n return self.api.run_command('/ppp/active/print')\n \n def get_hotspot_users(self) -> List[Dict]:\n \"\"\"获取 Hotspot 用户\"\"\"\n return self.api.run_command('/ip/hotspot/user/print')\n \n def get_hotspot_active(self) -> List[Dict]:\n \"\"\"获取活跃 Hotspot 会话\"\"\"\n return self.api.run_command('/ip/hotspot/active/print')\n \n def get_user_groups(self) -> List[Dict]:\n \"\"\"获取用户组\"\"\"\n return self.api.run_command('/user/group/print')\n\n\nclass QuickCommands:\n \"\"\"快捷命令集合\"\"\"\n \n def __init__(self, api: MikroTikAPI):\n self.api = api\n self.system = SystemCommands(api)\n self.firewall = FirewallCommands(api)\n self.network = NetworkCommands(api)\n self.user = UserCommands(api)\n self.routing = RoutingCommands(api)\n \n def status(self) -> Dict:\n \"\"\"获取设备完整状态\"\"\"\n return {\n 'identity': self.system.get_identity(),\n 'resource': self.system.get_resource(),\n 'interfaces': self.network.get_interfaces(),\n 'addresses': self.network.get_ip_addresses(),\n 'routes': self.network.get_routes(),\n }\n \n def print_status(self):\n \"\"\"打印设备状态(人类可读)\"\"\"\n identity = self.system.get_identity()\n resource = self.system.get_resource()\n \n print(\"=\" * 60)\n print(\"📡 MikroTik RouterOS 设备状态\")\n print(\"=\" * 60)\n print(f\" 设备名:{identity.get('name', 'N/A')}\")\n print(f\" 版本:{resource.get('version', 'N/A')}\")\n print(f\" 运行时间:{resource.get('uptime', 'N/A')}\")\n print(f\" CPU: {resource.get('cpu', 'N/A')} @ {resource.get('cpu-frequency', 'N/A')}MHz\")\n print(f\" CPU 负载:{resource.get('cpu-load', 'N/A')}%\")\n print(f\" 内存:{int(resource.get('free-memory', 0))/1024/1024:.1f}MB / \"\n f\"{int(resource.get('total-memory', 1))/1024/1024:.1f}MB\")\n print(f\" 存储:{int(resource.get('free-hdd-space', 0))/1024/1024:.1f}MB / \"\n f\"{int(resource.get('total-hdd-space', 1))/1024/1024:.1f}MB\")\n print(\"=\" * 60)\n \n # 接口状态\n print(\"\\n🔌 网络接口:\")\n interfaces = self.network.get_interfaces()\n for iface in interfaces:\n name = iface.get('name', 'unknown')\n running = iface.get('running', 'false') == 'true'\n status = \"✅\" if running else \"❌\"\n print(f\" {status} {name}\")\n \n # IP 地址\n print(\"\\n🌐 IP 地址:\")\n addresses = self.network.get_ip_addresses()\n for addr in addresses:\n print(f\" - {addr.get('address', 'N/A')} on {addr.get('interface', 'N/A')}\")\n \n # 默认路由\n print(\"\\n🛤️ 默认路由:\")\n routes = self.network.get_routes()\n for route in routes:\n if route.get('dst-address') == '0.0.0.0/0':\n print(f\" - 默认网关:{route.get('gateway', 'N/A')}\")\n \n print(\"=\" * 60)\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15748,"content_sha256":"a1d9bb07da15beae553d55d0b6592b767906cfb1ecf2268d8d765b30faf9585f"},{"filename":"mikrotik-api/README.md","content":"# MikroTik RouterOS API 工具\n\nMikroTik RouterOS API Python 客户端和命令行工具。\n\n## 安装\n\n无需安装,直接使用即可。依赖:Python 3.6+\n\n## 用法\n\n### 命令行工具\n\n```bash\ncd /root/.openclaw/workspace/tools/mikrotik-api\n\n# 查看设备状态\npython3 cli.py 192.168.1.1 status\n\n# 查看防火墙规则\npython3 cli.py 192.168.1.1 firewall\n\n# 查看网络接口\npython3 cli.py 192.168.1.1 interfaces\n\n# 查看路由表\npython3 cli.py 192.168.1.1 routes\n\n# 执行自定义命令\npython3 cli.py 192.168.1.1 cmd /system/resource/print\n\n# 指定用户名密码\npython3 cli.py 192.168.1.1 status -u admin -p yourpassword\n```\n\n### Python 库\n\n```python\nfrom mikrotik_api import MikroTikAPI, QuickCommands\n\n# 连接设备\napi = MikroTikAPI('192.168.1.1', username='admin', password='')\napi.connect()\napi.login()\n\n# 方法 1: 使用快捷命令\nquick = QuickCommands(api)\nquick.print_status() # 打印完整状态\n\n# 获取系统资源\nresource = quick.system.get_resource()\nprint(f\"CPU 负载:{resource['cpu-load']}%\")\n\n# 获取防火墙规则\nrules = quick.firewall.get_filter_rules()\n\n# 获取网络接口\ninterfaces = quick.network.get_interfaces()\n\n# 方法 2: 执行自定义命令\nresults = api.run_command('/system/resource/print')\nfor item in results:\n print(item)\n\n# 断开连接\napi.disconnect()\n```\n\n### 上下文管理器\n\n```python\nfrom mikrotik_api import MikroTikAPI, QuickCommands\n\nwith MikroTikAPI('192.168.1.1') as api:\n api.login()\n quick = QuickCommands(api)\n quick.print_status()\n# 自动断开连接\n```\n\n## API 参考\n\n### MikroTikAPI\n\n| 方法 | 说明 |\n|------|------|\n| `connect()` | 建立连接 |\n| `disconnect()` | 断开连接 |\n| `login()` | 登录 |\n| `run_command(cmd, args)` | 执行 API 命令 |\n\n### SystemCommands\n\n| 方法 | 说明 |\n|------|------|\n| `get_resource()` | 系统资源(CPU、内存等) |\n| `get_identity()` | 设备标识 |\n| `get_version()` | 系统版本 |\n| `get_health()` | 健康状态(温度、电压) |\n| `reboot()` | 重启设备 |\n| `shutdown()` | 关闭设备 |\n\n### FirewallCommands\n\n| 方法 | 说明 |\n|------|------|\n| `get_filter_rules()` | 过滤规则 |\n| `get_nat_rules()` | NAT 规则 |\n| `get_mangle_rules()` | Mangle 规则 |\n| `get_active_connections()` | 活动连接 |\n| `get_connection_stats()` | 连接统计 |\n\n### NetworkCommands\n\n| 方法 | 说明 |\n|------|------|\n| `get_interfaces()` | 网络接口 |\n| `get_ip_addresses()` | IP 地址配置 |\n| `get_routes()` | 路由表 |\n| `get_dns()` | DNS 配置 |\n| `get_dhcp_leases()` | DHCP 租约 |\n| `get_arp()` | ARP 表 |\n| `get_neighbors()` | 邻居发现 |\n\n## 常用命令路径\n\n```\n# 系统\n/system/resource/print\n/system/identity/print\n/system/routerboard/print\n/system/health/print\n\n# 接口\n/interface/print\n/interface/ethernet/print\n\n# IP\n/ip/address/print\n/ip/route/print\n/ip/dns/print\n/ip/dhcp-server/lease/print\n/ip/arp/print\n/ip/firewall/connection/print\n\n# 防火墙\n/ip/firewall/filter/print\n/ip/firewall/nat/print\n/ip/firewall/mangle/print\n\n# 其他\n/ip/neighbor/print\n/system/logging/print\n```\n\n## 注意事项\n\n1. **API 端口**: 默认 8728 (SSL 用 8729)\n2. **认证**: 支持空密码和 MD5 challenge-response\n3. **协议**: 二进制 length-prefixed 格式\n4. **超时**: 默认 5 秒,可根据网络情况调整\n\n## 示例输出\n\n```\n============================================================\n📡 MikroTik RouterOS 设备状态\n============================================================\n 设备名:MikroTik\n 版本:7.21.2 (stable)\n 运行时间:1w2d8h45m38s\n CPU: MIPS 1004Kc V2.15 @ 880MHz\n CPU 负载:2%\n 内存:64.7MB / 268MB\n 存储:3.8MB / 16MB\n============================================================\n```\n\n## 作者\n\n虾哥 🤖\n\n## 许可证\n\nMIT\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3783,"content_sha256":"01371fcc0bd1e8ab45c3281035a7e54b03ce374c1777174277e7850aa6824b9c"},{"filename":"mikrotik-api/scanner.py","content":"#!/usr/bin/env python3\n\"\"\"\nMikroTik 设备扫描器 - 类似 Winbox 的扫描功能\n\n从本地主机直接扫描局域网中的 MikroTik 设备\n不依赖任何已知的 MikroTik 设备或 API 连接\n\"\"\"\n\nimport socket\nimport struct\nimport time\nimport subprocess\nfrom typing import List, Dict, Optional\n\n\nclass MikroTikScanner:\n \"\"\"MikroTik 设备扫描器(独立扫描,不依赖 API)\"\"\"\n \n # MikroTik 发现协议端口\n WINBOX_PORT = 5678\n BROADCAST_ADDR = '255.255.255.255'\n \n # MikroTik OUI 前缀(用于识别)\n # 这些是 MikroTik 官方的 MAC 地址前缀,用于识别设备品牌\n MIKROTIK_OUIS = [\n '00:0C:42', '4C:5E:0C', 'D4:CA:6D',\n '78:8B:77', '84:D1:54', 'B8:69:F4'\n ]\n \n def __init__(self, timeout: float = 2.0):\n \"\"\"\n 初始化扫描器\n \n Args:\n timeout: 扫描超时(秒)\n \"\"\"\n self.timeout = timeout\n self.discovered_devices: List[Dict] = []\n \n def get_local_subnets(self) -> List[str]:\n \"\"\"\n 获取本地所有网段\n \n Returns:\n 网段列表(如 ['192.168.1.0/24', '10.0.0.0/24'])\n \"\"\"\n subnets = []\n try:\n result = subprocess.run(['ip', '-o', 'addr', 'show'], \n capture_output=True, text=True, timeout=5)\n \n for line in result.stdout.split('\\n'):\n if 'inet ' in line and '/' in line:\n parts = line.split()\n for i, part in enumerate(parts):\n if '/' in part and '.' in part:\n ip, prefix = part.split('/')\n if int(prefix) == 24:\n # 排除 lo 和虚拟接口\n iface = parts[1] if len(parts) > 1 else ''\n if iface not in ['lo', 'docker0'] and not iface.startswith('br-'):\n octets = ip.split('.')\n subnet = f\"{octets[0]}.{octets[1]}.{octets[2]}.0/{prefix}\"\n if subnet not in subnets:\n subnets.append(subnet)\n except Exception as e:\n print(f\"⚠️ 获取本地网段失败:{e}\")\n \n return subnets\n \n def get_local_ips(self) -> List[str]:\n \"\"\"\n 获取本机所有 IP 地址\n \n Returns:\n IP 地址列表\n \"\"\"\n ips = []\n try:\n result = subprocess.run(['hostname', '-I'], \n capture_output=True, text=True, timeout=5)\n ips = result.stdout.strip().split()\n except:\n pass\n return ips\n \n def scan_arp_table(self) -> List[Dict]:\n \"\"\"\n 从本地 ARP 表发现 MikroTik 设备\n \n Returns:\n 发现的设备列表\n \"\"\"\n devices = []\n local_ips = self.get_local_ips()\n \n try:\n result = subprocess.run(['ip', 'neigh'], \n capture_output=True, text=True, timeout=5)\n \n for line in result.stdout.split('\\n'):\n parts = line.split()\n if len(parts) >= 5:\n ip = parts[0]\n \n # 排除本机 IP\n if ip in local_ips:\n continue\n \n if parts[1] == 'dev':\n # 查找 MAC 地址\n mac = ''\n for i, part in enumerate(parts):\n if part == 'lladdr':\n mac = parts[i+1].upper() if i+1 \u003c len(parts) else ''\n break\n \n if mac and mac != '00:00:00:00:00:00':\n # 检查是否是 MikroTik OUI\n is_mikrotik = any(mac.startswith(oui) for oui in self.MIKROTIK_OUIS)\n \n if is_mikrotik:\n device = {\n 'ip': ip,\n 'mac': mac,\n 'identity': 'MikroTik',\n 'model': 'Unknown',\n 'version': '',\n 'source': 'arp'\n }\n devices.append(device)\n print(f\" ✅ 发现:{ip} ({mac})\")\n except Exception as e:\n print(f\"⚠️ ARP 表扫描失败:{e}\")\n \n return devices\n \n def send_discovery_request(self, subnet: str) -> List[Dict]:\n \"\"\"\n 发送 MikroTik 发现请求到指定子网\n \n Args:\n subnet: 子网地址(如 '192.168.1.0/24')\n \n Returns:\n 发现的设备列表\n \"\"\"\n devices = []\n \n try:\n sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n sock.settimeout(1.0)\n \n # 构建发现请求报文\n my_mac = b'\\x00\\x00\\x00\\x00\\x00\\x00'\n proto_type = struct.pack('!H', 0x0001)\n discovery_msg = my_mac + proto_type\n \n # 获取子网中的所有 IP\n ips = self._get_subnet_ips(subnet)\n \n # 发送到每个 IP\n for ip in ips:\n try:\n sock.sendto(discovery_msg, (ip, self.WINBOX_PORT))\n time.sleep(0.01)\n except:\n pass\n \n # 接收响应\n start_time = time.time()\n while time.time() - start_time \u003c 3.0:\n try:\n data, addr = sock.recvfrom(2048)\n device = self._parse_discovery_packet(data, addr[0])\n \n if device:\n mac = device.get('mac', '')\n if mac and not any(d.get('mac') == mac for d in devices):\n devices.append(device)\n print(f\" ✅ 发现:{device.get('identity', 'Unknown')} ({addr[0]})\")\n except socket.timeout:\n break\n except:\n pass\n \n sock.close()\n \n except Exception as e:\n print(f\"⚠️ 发送请求失败:{e}\")\n \n return devices\n \n def _get_subnet_ips(self, subnet: str) -> List[str]:\n \"\"\"获取子网中的所有 IP 地址\"\"\"\n network, prefix = subnet.split('/')\n prefix = int(prefix)\n \n network_bytes = struct.unpack('!I', socket.inet_aton(network))[0]\n mask = (0xFFFFFFFF \u003c\u003c (32 - prefix)) & 0xFFFFFFFF\n network_masked = network_bytes & mask\n broadcast = network_masked | (~mask & 0xFFFFFFFF)\n \n start_ip = network_masked + 1\n end_ip = broadcast - 1\n \n ips = []\n for ip_int in range(start_ip, end_ip + 1):\n ips.append(socket.inet_ntoa(struct.pack('!I', ip_int)))\n \n return ips\n \n def _parse_discovery_packet(self, data: bytes, source_ip: str) -> Optional[Dict]:\n \"\"\"解析 MikroTik 发现协议报文\"\"\"\n if len(data) \u003c 8:\n return None\n \n device = {\n 'ip': source_ip,\n 'source': 'broadcast'\n }\n \n try:\n offset = 6 # 跳过目标 MAC\n proto_type = struct.unpack('!H', data[offset:offset+2])[0]\n offset += 2\n \n # TLV 字段映射\n TLV_IDENTITY = 0x0001\n TLV_MAC = 0x0006\n TLV_PLATFORM = 0x0009\n TLV_VERSION = 0x0008\n TLV_BOARD = 0x000A\n \n while offset \u003c len(data) - 4:\n tlv_type = struct.unpack('!H', data[offset:offset+2])[0]\n offset += 2\n \n tlv_len = struct.unpack('!H', data[offset:offset+2])[0]\n offset += 2\n \n if offset + tlv_len > len(data):\n break\n \n value = data[offset:offset+tlv_len]\n offset += tlv_len\n \n if tlv_type == TLV_IDENTITY:\n device['identity'] = value.decode('utf-8', errors='ignore').strip()\n elif tlv_type == TLV_MAC:\n if len(value) == 6:\n device['mac'] = ':'.join([f'{b:02X}' for b in value])\n elif tlv_type == TLV_PLATFORM:\n device['platform'] = value.decode('utf-8', errors='ignore').strip()\n elif tlv_type == TLV_VERSION:\n device['version'] = value.decode('utf-8', errors='ignore').strip()\n elif tlv_type == TLV_BOARD:\n device['board'] = value.decode('utf-8', errors='ignore').strip()\n \n # 使用 board 作为型号\n if 'board' in device:\n device['model'] = device['board']\n elif 'platform' in device and device['platform'] != 'MikroTik':\n device['model'] = device['platform']\n else:\n device['model'] = 'Unknown'\n \n if 'mac' in device:\n return device\n \n except Exception as e:\n pass\n \n return None\n \n def scan(self) -> List[Dict]:\n \"\"\"\n 执行完整扫描\n \n Returns:\n 发现的设备列表\n \"\"\"\n all_devices = {}\n \n # 获取本地网段\n subnets = self.get_local_subnets()\n if not subnets:\n print(\"⚠️ 无法获取本地网段\")\n return []\n \n print(f\"📡 扫描 {len(subnets)} 个本地网段:\")\n for subnet in subnets:\n print(f\" - {subnet}\")\n print()\n \n # 方法 1: 扫描 ARP 表\n print(\"🔍 方法 1: 扫描 ARP 表...\")\n arp_devices = self.scan_arp_table()\n for dev in arp_devices:\n mac = dev.get('mac', '')\n if mac:\n all_devices[mac] = dev\n \n # 方法 2: 主动发现请求\n if not arp_devices:\n print(\"\\n🔍 方法 2: 发送发现请求...\")\n for subnet in subnets:\n print(f\" 扫描:{subnet}\")\n devices = self.send_discovery_request(subnet)\n for dev in devices:\n mac = dev.get('mac', '')\n if mac and mac not in all_devices:\n all_devices[mac] = dev\n \n self.discovered_devices = list(all_devices.values())\n return self.discovered_devices\n \n def format_results(self) -> str:\n \"\"\"格式化扫描结果\"\"\"\n if not self.discovered_devices:\n return \" (未发现设备)\"\n \n lines = []\n lines.append(f\"\\n共发现 {len(self.discovered_devices)} 个设备:\\n\")\n \n for i, device in enumerate(self.discovered_devices, 1):\n identity = device.get('identity', 'Unknown')\n ip = device.get('ip', 'N/A')\n mac = device.get('mac', '')\n model = device.get('model', '')\n version = device.get('version', '')\n source = device.get('source', 'unknown')\n \n lines.append(f\" [{i}] {identity}\")\n lines.append(f\" IP: {ip}\")\n if mac:\n lines.append(f\" MAC: {mac}\")\n if model and model != 'Unknown':\n lines.append(f\" 型号:{model}\")\n if version:\n lines.append(f\" 版本:{version}\")\n \n if source == 'arp':\n lines.append(f\" 来源:ARP 表\")\n elif source == 'broadcast':\n lines.append(f\" 来源:广播发现\")\n else:\n lines.append(f\" 来源:主动发现\")\n \n lines.append(\"\")\n \n return \"\\n\".join(lines)\n\n\ndef scan_network() -> str:\n \"\"\"\n 扫描网络中的 MikroTik 设备\n \n Returns:\n 格式化的扫描结果\n \"\"\"\n scanner = MikroTikScanner(timeout=2.0)\n scanner.scan()\n return scanner.format_results()\n\n\nif __name__ == '__main__':\n print(scan_network())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":12759,"content_sha256":"98f6ccabc93215b24831e164da7e448d3a38c585f715797e5b25d4e044f9de60"},{"filename":"package.json","content":"{\n \"name\": \"mikrotik\",\n \"displayName\": \"MikroTik RouterOS 管理\",\n \"version\": \"1.8.3\",\n \"description\": \"通过 API 连接和管理 MikroTik RouterOS 设备。支持查看设备状态、防火墙规则、网络配置,执行自定义 RouterOS 命令。\",\n \"author\": \"2944721178\",\n \"license\": \"MIT\",\n \"tags\": [\"mikrotik\", \"routeros\", \"network\", \"firewall\", \"router\"],\n \"repository\": \"https://github.com/2944721178/openclaw-mikrotik-skill\",\n \"main\": \"handler.py\",\n \"engines\": {\n \"openclaw\": \">=2026.3.2\",\n \"python\": \">=3.6\"\n }\n}\n","content_type":"application/json; charset=utf-8","language":"json","size":545,"content_sha256":"f2215ac8038f29d6f5b84ed56f9a0aff1dca351cee9bf16467d96a8c0f40d98e"},{"filename":"README.md","content":"# MikroTik RouterOS Skill for OpenClaw\n\n通过 API 连接和管理 MikroTik RouterOS 设备的 OpenClaw Skill。\n\n## 功能\n\n- ✅ 查看设备状态(系统信息、CPU、内存、存储)\n- ✅ 查看防火墙规则(filter、NAT、mangle)\n- ✅ 查看网络配置(接口、IP 地址、路由、DNS)\n- ✅ 执行自定义 RouterOS 命令\n- ✅ 支持多设备连接\n- ✅ 网络扫描(类似 Winbox,无需配置)\n- ✅ 交互式开局流程\n\n## 🚀 快速开始(新用户开局)\n\n### 方式一:交互式开局(推荐)\n\n**1. 说出口令**:\n```\nmikrotik 开局\n```\n\n**2. AI 自动扫描**:\n```\n📡 扫描完成!发现 3 个设备:\n\n[1] 192.168.1.10 (00:0C:42:AA:BB:CC)\n[2] 192.168.1.20 (4C:5E:0C:DD:EE:FF)\n[3] 192.168.1.1 (D4:CA:6D:11:22:33)\n```\n\n**3. 告诉 AI 账号密码**:\n```\n全部使用 admin/空密码\n```\n\n**4. AI 测试连接并保存**:\n```\n✅ 连接成功!配置已保存到 TOOLS.md\n```\n\n**5. 开始使用**:\n```\n查看 device1 状态\nmikrotik office firewall\n```\n\n### 方式二:手动配置\n\n在 `~/.openclaw/workspace/TOOLS.md` 中添加:\n\n```markdown\n### MikroTik 设备\n- **office**: 192.168.1.1, admin, 空密码\n- **home**: 192.168.88.1, admin, yourpassword\n```\n\n## 安装\n\n### 方法 1: 手动安装(当前可用)\n\n```bash\n# 克隆仓库\ngit clone https://github.com/YOUR_USERNAME/openclaw-mikrotik-skill.git\ncd openclaw-mikrotik-skill\n\n# 复制到 OpenClaw skills 目录\ncp -r mikrotik /usr/lib/node_modules/openclaw/skills/\n\n# 重启 OpenClaw Gateway\nopenclaw gateway restart\n```\n\n### 方法 2: 通过 ClawHub(即将上线)\n\n```bash\n# 待发布到 ClawHub 后可用\nnpx clawhub install mikrotik\n```\n\n\n\n## 配置\n\n在 `TOOLS.md` 中添加 MikroTik 设备信息:\n\n```markdown\n### MikroTik 设备\n\n- **office**: 192.168.1.1, admin, 空密码\n- **home**: 192.168.88.1, admin, yourpassword\n```\n\n## 用法\n\n### 自然语言命令\n\n```\n查看 mikrotik 设备状态\nmikrotik 防火墙配置\n检查路由器运行情况\n查看网络接口\n查看无线客户端\nmikrotik office 客户端\nmikrotik ap wifi\n在 mikrotik 上执行 /system/resource/print\n```\n\n### 可用命令\n\n| 命令 | 说明 |\n|------|------|\n| `状态` / `status` | 查看设备状态(CPU、内存、运行时间) |\n| `防火墙` / `firewall` | 查看防火墙规则(filter、NAT) |\n| `接口` / `interface` | 查看网络接口列表 |\n| `客户端` / `client` / `无线` / `wifi` | **查看无线客户端连接(CAPsMAN)** ⭐ |\n| `路由` / `route` | 查看路由表 |\n| `DHCP` | 查看 DHCP 配置和租约 |\n| `ARP` | 查看 ARP 表 |\n| `流量` / `traffic` | 查看接口流量统计 |\n| `WireGuard` / `wg` | 查看 WireGuard 隧道状态 |\n| `扫描` / `scan` | 扫描局域网设备(无需配置) |\n\n### 命令行工具\n\n```bash\ncd mikrotik-api\npython3 cli.py 192.168.1.1 status # 查看设备状态\npython3 cli.py 192.168.1.1 firewall # 查看防火墙\npython3 cli.py 192.168.1.1 interfaces # 查看接口\npython3 cli.py 192.168.1.1 routes # 查看路由\n```\n\n### Python API\n\n```python\nfrom mikrotik_api import MikroTikAPI, QuickCommands\n\nwith MikroTikAPI('192.168.1.1') as api:\n api.login()\n quick = QuickCommands(api)\n quick.print_status()\n```\n\n## 示例输出\n\n### 设备状态\n\n```\n📡 MikroTik RouterOS 设备状态\n============================================================\n 设备名:OFFICE\n 版本:7.21.2 (stable)\n 运行时间:1w2d9h9m39s\n CPU: MIPS 1004Kc V2.15 @ 880MHz\n CPU 负载:1%\n 内存:61.6MB / 256.0MB\n 存储:3.6MB / 16.0MB\n============================================================\n```\n\n### 无线客户端(v1.8.1 新增)⭐\n\n```\n📶 无线客户端 (CAPsMAN)\n============================================================\n\n✅ 已连接 2 个无线客户端:\n\n 【客户端 1】\n MAC: 00:11:22:33:44:55\n SSID: MyWiFi | 接口:cap2\n 信号:-49 ⭐⭐⭐\n 速率:TX 702Mbps-80MHz/2S | RX 585Mbps-80MHz/2S\n 连接时间:1d47m\n IP 地址:192.168.1.101\n 流量:TX 1.8GB / RX 1.2GB\n\n 【客户端 2】\n MAC: AA:BB:CC:DD:EE:FF\n SSID: MyWiFi | 接口:cap2\n 信号:-34 ⭐⭐⭐⭐⭐\n 速率:TX 866.6Mbps-80MHz/2S/SGI | RX 702Mbps-80MHz/2S\n 连接时间:20m\n IP 地址:192.168.1.102\n 流量:TX 29.7MB / RX 892KB\n```\n\n## 依赖\n\n- Python 3.6+\n- OpenClaw 2026.3.2+\n- MikroTik RouterOS API 已启用(默认端口 8728)\n\n## 注意事项\n\n1. 确保 RouterOS 的 API 服务已启用(`/ip/service/print` 查看)\n2. 默认端口 8728,SSL 端口 8729\n3. 空密码设备注意安全风险\n4. 部分命令需要管理员权限\n\n## 文件结构\n\n```\nmikrotik/\n├── SKILL.md # Skill 说明和配置\n├── handler.py # 命令处理器\n├── README.md # 本文件\n└── mikrotik-api/ # Python API 客户端\n ├── __init__.py\n ├── client.py # 核心 API 客户端\n ├── commands.py # 常用命令封装\n ├── cli.py # 命令行工具\n └── README.md # API 文档\n```\n\n## 开发\n\n### 测试\n\n```bash\ncd mikrotik-api\npython3 cli.py 192.168.1.1 status\n```\n\n### 添加新功能\n\n1. 在 `commands.py` 中添加新方法\n2. 在 `handler.py` 中添加命令处理\n3. 更新 `SKILL.md` 文档\n\n## 贡献\n\n欢迎提交 Issue 和 Pull Request!\n\n## 许可证\n\nMIT License\n\n## 作者\n\n虾哥 🤖\n\n## 相关链接\n\n- [OpenClaw 文档](https://docs.openclaw.ai)\n- [MikroTik API 文档](https://help.mikrotik.com/docs/display/ROS/API)\n- [ClawHub](https://clawhub.com)\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5562,"content_sha256":"8c863473717245b7da41b29478054ada07a161916e7f3e74168c6da0b40b4b82"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"MikroTik RouterOS Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"通过 API 连接和管理 MikroTik RouterOS 设备。","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"功能","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"查看设备状态(系统信息、CPU、内存、存储)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"查看防火墙规则(filter、NAT、mangle)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"查看网络配置(接口、IP 地址、路由、DNS)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"执行自定义 RouterOS 命令","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"支持多设备连接","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"配置","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"方式一:TOOLS.md(推荐)","type":"text"}]},{"type":"paragraph","content":[{"text":"在 ","type":"text"},{"text":"~/.openclaw/workspace/TOOLS.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中添加设备信息:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"markdown"},"content":[{"text":"### MikroTik 设备\n\n- **office**: 192.168.1.1, admin, 空密码\n- **home**: 192.168.88.1, admin, yourpassword\n- **branch**: 192.168.2.1, admin, complex_password_123","type":"text"}]},{"type":"paragraph","content":[{"text":"密码格式说明","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"空密码:写 ","type":"text"},{"text":"空密码","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"无密码","type":"text","marks":[{"type":"code_inline"}]},{"text":"、","type":"text"},{"text":"none","type":"text","marks":[{"type":"code_inline"}]},{"text":" 或留空","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"有密码:直接写密码字符串","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"方式二:环境变量","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"export MIKROTIK_HOST=192.168.1.1\nexport MIKROTIK_USER=admin\nexport MIKROTIK_PASS= # 空密码\n# 或\nexport MIKROTIK_PASS=yourpassword # 有密码","type":"text"}]},{"type":"paragraph","content":[{"text":"优先级","type":"text","marks":[{"type":"strong"}]},{"text":":环境变量 > TOOLS.md > 默认值","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"用法","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📊 设备状态","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看 mikrotik 设备状态\n查看 office mikrotik 状态\n检查路由器运行情况","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔥 防火墙","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看防火墙规则\nmikrotik 防火墙配置\n显示 NAT 规则\n查看 office 防火墙","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔌 网络接口","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看网络接口\nmikrotik 接口列表\n显示 IP 地址配置","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📋 DHCP","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看 DHCP 配置\n显示 DHCP 租约\nmikrotik dhcp","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📡 ARP 表","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看 ARP 表\nmikrotik arp\n显示 ARP 缓存","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔐 WireGuard","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看 WireGuard 配置\nmikrotik wireguard\n显示 VPN 对等体","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"👤 用户","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看用户配置\nmikrotik users\n显示 PPP 用户","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📝 日志","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看系统日志\nmikrotik logs\n显示最近日志","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔧 服务","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看系统服务\nmikrotik services\n显示 API/SSH 端口","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📈 流量统计","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看接口流量\nmikrotik traffic\n显示流量统计","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔌 接口详情","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看接口详细信息\nmikrotik interface detail\n查看 ether1 接口详情","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🏷️ VLAN","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看 VLAN 配置\nmikrotik vlan\n显示 VLAN 列表","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🌉 桥接","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看桥接配置\nmikrotik bridge\n显示桥接端口","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📊 队列/带宽","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看队列配置\nmikrotik queue\n显示带宽限制\n查看限速规则","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🌐 路由","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看路由配置\nmikrotik route\n显示 OSPF 状态\n查看 BGP 对等体","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🌡️ 系统健康","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看系统健康\nmikrotik health\n显示温度/电压/风扇","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📅 计划任务","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看计划任务\nmikrotik scheduler\n显示定时任务","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📡 邻居设备","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看邻居设备\nmikrotik neighbors\n显示网络设备发现","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🔗 活动连接","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看活动连接\nmikrotik connections\n显示连接统计","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🏓 Ping 测试","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"ping 8.8.8.8\nmikrotik ping 1.1.1.1","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"📡 网络扫描","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"扫描局域网\nmikrotik scan\n扫描 192.168.1.0/24\n查找 MikroTik 设备","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🎯 自定义命令","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"在 mikrotik 上执行 /system/resource/print\n运行 routeros 命令 /ip/address/print\n在 office 设备上执行 /interface/print","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"🖥️ 多设备支持","type":"text"}]},{"type":"paragraph","content":[{"text":"如果配置了多个设备,可以在命令中指定设备名称:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"查看机房 mikrotik 状态\n查看 home 防火墙规则","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"依赖","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Python 3.6+","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"设备 API 已启用(默认端口 8728)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"网络可达","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"文件结构","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"skills/mikrotik/\n├── SKILL.md # 技能说明(本文件)\n├── handler.py # 命令处理器\n└── mikrotik-api/ # API 客户端库(复用现有)\n ├── __init__.py\n ├── client.py\n ├── commands.py\n └── cli.py","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"示例响应","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"📡 MikroTik RouterOS 设备状态\n==================================================\n 设备名:OFFICE\n 版本:7.21.2 (stable)\n 运行时间:1w2d9h9m39s\n CPU: MIPS 1004Kc V2.15 @ 880MHz\n CPU 负载:1%\n 内存:61.6MB / 256.0MB\n 存储:3.6MB / 16.0MB\n==================================================","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"注意事项","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"确保 RouterOS 的 API 服务已启用(","type":"text"},{"text":"/ip/service/print","type":"text","marks":[{"type":"code_inline"}]},{"text":" 查看)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"默认端口 8728,SSL 端口 8729","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"空密码设备注意安全风险","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"部分命令需要管理员权限","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"响应解析已优化,支持多条目返回(接口列表、路由表、ARP 表等)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"更新日志","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.8.2 (2026-03-06) 🔥","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🔧 ","type":"text"},{"text":"修复固件显示错误","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"正确解读 RouterBOARD 固件字段","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"upgrade-firmware","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 当前运行的固件 ✅","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"current-firmware","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 出厂预装版本","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"📋 添加设备型号和序列号显示","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"📊 优化状态输出格式","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.8.1 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"📶 ","type":"text"},{"text":"新增无线客户端查询(CAPsMAN)","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"查看已连接的无线客户端","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"显示 MAC、SSID、信号强度、速率","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"显示连接时间、IP 地址、流量统计","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"信号强度星级评级(⭐⭐⭐⭐⭐)","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🔍 支持命令:客户端/无线/wifi/client","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.8.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🎯 ","type":"text"},{"text":"新用户开局优化","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"交互式配置流程","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"扫描 → 选择 → 测试 → 保存 → 验证","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"修复设备名称匹配正则","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"扫描功能完全独立","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"📝 更新 README.md 开局示例","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.7.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🔍 ","type":"text"},{"text":"优化网络扫描功能","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"完全独立扫描,不依赖 API 配置","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"自动排除本机 IP","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"支持多网段扫描","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.6.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增网络扫描功能(类似 Winbox)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 支持广播报文监听发现设备","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 支持邻居发现、ARP 表、网关多来源扫描","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 显示设备 MAC、IP、型号、版本信息","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 智能去重(基于 MAC 地址)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.5.0 (2026-03-06) [已推送]","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增高级路由功能查看(OSPF、BGP)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增系统健康监控(温度、电压、风扇)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增计划任务查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增邻居设备发现","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增活动连接统计","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增 Ping 测试功能","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 智能温度告警(>70°C🔥, >50°C⚠️)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.4.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增队列/带宽限制查看(Simple Queues)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增队列树配置查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增队列类型显示","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 带宽单位自动转换(Mbps/Gbps)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.3.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增接口流量统计(接收/发送字节数、包数、错误/丢弃)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增接口详细信息查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增 VLAN 配置查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增桥接配置和端口列表","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 流量单位自动转换(B/KB/MB/GB)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.2.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增 DHCP 配置查看(租约、服务器)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增 ARP 表查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增 WireGuard 对等体状态","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增用户配置(系统用户、PPP 用户)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增系统日志查看","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"🆕 新增系统服务查看(API、SSH、WWW 等)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.1.0 (2026-03-06)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ 修复凭证配置:支持 TOOLS.md 和环境变量","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ 修复响应解析:完整支持多条目返回","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ 改进连接可靠性:改用非阻塞模式 + select 超时","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"✅ 增强错误提示:友好的配置缺失提示","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"v1.0.0 (2026-03-05)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"初始版本","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"作者","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"虾哥 🤖","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"mikrotik","author":"@skillopedia","source":{"stars":3,"repo_name":"openclaw-mikrotik-skill","origin_url":"https://github.com/2944721178/openclaw-mikrotik-skill/blob/HEAD/SKILL.md","repo_owner":"2944721178","body_sha256":"e2bdd59ef63018bf4130c6965197a78ab1804280097dfceb7bb2d64bf32bc695","cluster_key":"f9e69645e7aefa1d5babbc6ba15fc0ac04c4801ddf1ef011759a757b4633e5f9","clean_bundle":{"format":"clean-skill-bundle-v1","source":"2944721178/openclaw-mikrotik-skill/SKILL.md","attachments":[{"id":"a121759b-4c14-5e14-b5e8-74adfd424cf0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a121759b-4c14-5e14-b5e8-74adfd424cf0/attachment","path":".gitignore","size":25,"sha256":"927da10146f467da2182387616f176123812a330f575bbec2c6aa339ee5dcf84","contentType":"text/plain; charset=utf-8"},{"id":"c4267917-6b05-54ba-ba5d-a7783a99642c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c4267917-6b05-54ba-ba5d-a7783a99642c/attachment.md","path":"README.md","size":5562,"sha256":"8c863473717245b7da41b29478054ada07a161916e7f3e74168c6da0b40b4b82","contentType":"text/markdown; charset=utf-8"},{"id":"cc727e0c-b045-5ebb-9c26-21961a1e12a9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cc727e0c-b045-5ebb-9c26-21961a1e12a9/attachment.py","path":"handler.py","size":46740,"sha256":"943c6a2886e4ea699074eeac143a727ac7f86834829630b8d1b62e991574fdc6","contentType":"text/x-python; charset=utf-8"},{"id":"20e54afa-26ce-54cf-99f6-e352341acede","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/20e54afa-26ce-54cf-99f6-e352341acede/attachment.md","path":"mikrotik-api/README.md","size":3783,"sha256":"01371fcc0bd1e8ab45c3281035a7e54b03ce374c1777174277e7850aa6824b9c","contentType":"text/markdown; charset=utf-8"},{"id":"aa1789a3-1cf9-5cba-ad98-2d7c59fc7bb0","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aa1789a3-1cf9-5cba-ad98-2d7c59fc7bb0/attachment.py","path":"mikrotik-api/__init__.py","size":720,"sha256":"609facb88d504406df1e973bc91501e7b6b5d4382a14a0adfa568e703551e4da","contentType":"text/x-python; charset=utf-8"},{"id":"b455bb85-00fe-5106-bad4-610dd21e0584","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b455bb85-00fe-5106-bad4-610dd21e0584/attachment.pyc","path":"mikrotik-api/__pycache__/client.cpython-312.pyc","size":10270,"sha256":"be2485f6d2fbe56508af4a7d69c7c08fd674edfb2ebf3e6cfb48c88b9dc9569b","contentType":"application/x-python-code"},{"id":"f3fe155f-38cd-5a99-acb5-b25ca51b551e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f3fe155f-38cd-5a99-acb5-b25ca51b551e/attachment.pyc","path":"mikrotik-api/__pycache__/commands.cpython-312.pyc","size":27386,"sha256":"d398710f0bf7504de7d89ab2d91a30f99e7847a62e318e1610f0ab9f995ae63f","contentType":"application/x-python-code"},{"id":"f72fdabf-972f-5e89-a8e8-b354116fc59d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f72fdabf-972f-5e89-a8e8-b354116fc59d/attachment.py","path":"mikrotik-api/cli.py","size":5092,"sha256":"474d08cfad3daea119fd8677428f9943a0e48465df6267008c819ee8a60831f3","contentType":"text/x-python; charset=utf-8"},{"id":"b7383f3e-5bf4-595e-af95-e71c6fb5bb3c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b7383f3e-5bf4-595e-af95-e71c6fb5bb3c/attachment.py","path":"mikrotik-api/client.py","size":9571,"sha256":"c77b0429c2fb74d3cbda881dfa3592c6a6c3bd78d0d973eaa7baa654e7c347db","contentType":"text/x-python; charset=utf-8"},{"id":"62ff5ace-8064-5bb5-b9e3-ecc39223a748","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/62ff5ace-8064-5bb5-b9e3-ecc39223a748/attachment.py","path":"mikrotik-api/commands.py","size":15748,"sha256":"a1d9bb07da15beae553d55d0b6592b767906cfb1ecf2268d8d765b30faf9585f","contentType":"text/x-python; charset=utf-8"},{"id":"fd1b2251-33fa-571f-b5f9-7b358c876bcc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fd1b2251-33fa-571f-b5f9-7b358c876bcc/attachment.py","path":"mikrotik-api/scanner.py","size":12759,"sha256":"98f6ccabc93215b24831e164da7e448d3a38c585f715797e5b25d4e044f9de60","contentType":"text/x-python; charset=utf-8"},{"id":"914ebeda-d29c-55ad-a242-0e0fd4d72150","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/914ebeda-d29c-55ad-a242-0e0fd4d72150/attachment.json","path":"package.json","size":545,"sha256":"f2215ac8038f29d6f5b84ed56f9a0aff1dca351cee9bf16467d96a8c0f40d98e","contentType":"application/json; charset=utf-8"}],"bundle_sha256":"96115b93be2cceb0ff9e0e4d0419dc89ee4e4f738d77d6ee2801403638573aea","attachment_count":12,"text_attachments":9,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":3,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"integrations-apis","category_label":"Integrations"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"integrations-apis","import_tag":"clean-skills-v1","description":"通过 API 连接和管理 MikroTik RouterOS 设备。支持查看设备状态、防火墙规则、网络配置,执行自定义 RouterOS 命令。"}},"renderedAt":1782981072508}

MikroTik RouterOS Skill 通过 API 连接和管理 MikroTik RouterOS 设备。 功能 - 查看设备状态(系统信息、CPU、内存、存储) - 查看防火墙规则(filter、NAT、mangle) - 查看网络配置(接口、IP 地址、路由、DNS) - 执行自定义 RouterOS 命令 - 支持多设备连接 配置 方式一:TOOLS.md(推荐) 在 中添加设备信息: 密码格式说明 : - 空密码:写 、 、 或留空 - 有密码:直接写密码字符串 方式二:环境变量 优先级 :环境变量 TOOLS.md 默认值 用法 📊 设备状态 🔥 防火墙 🔌 网络接口 📋 DHCP 📡 ARP 表 🔐 WireGuard 👤 用户 📝 日志 🔧 服务 📈 流量统计 🔌 接口详情 🏷️ VLAN 🌉 桥接 📊 队列/带宽 🌐 路由 🌡️ 系统健康 📅 计划任务 📡 邻居设备 🔗 活动连接 🏓 Ping 测试 📡 网络扫描 🎯 自定义命令 🖥️ 多设备支持 如果配置了多个设备,可以在命令中指定设备名称: 依赖 - Python 3.6+ - 设备 API 已启用(默认端口 8728) - 网络可达 文件结构 示例响应 注意事项 1. 确保 RouterOS 的 API 服务已启用( 查看) 2. 默认端口 8728,S…