Add Agent Skills documentation and implement g3fo-bug-finder skill

- Created README.md to introduce the Agent Skills repository and its functionalities.
- Added g3fo-bug-finder skill with detailed SKILL.md for automated bug diagnosis on remote servers.
- Included service inventory reference for mapping services to server configurations.
- Implemented scripts for fetching logs and configurations from remote servers using SSH and Nacos API.
- Provided usage instructions and environment setup for Python dependencies.
This commit is contained in:
2026-01-16 17:56:29 +08:00
parent 02bf619c65
commit b88e934f86
5 changed files with 84 additions and 4 deletions
@@ -0,0 +1,47 @@
#!/usr/bin/env python3
import sys
import argparse
import urllib.request
import urllib.error
def fetch_config(ip, data_id, group, tenant):
url = f"http://{ip}:8848/nacos/v1/cs/configs?dataId={data_id}&group={group}&tenant={tenant}"
print(f"Trying to fetch config from {url}...")
try:
with urllib.request.urlopen(url, timeout=5) as response:
if response.status == 200:
return response.read().decode('utf-8')
except urllib.error.URLError as e:
print(f"Failed to connect to {ip}: {e}")
except Exception as e:
print(f"Error fetching config from {ip}: {e}")
return None
def main():
parser = argparse.ArgumentParser(description="Fetch configuration from Nacos via HTTP.")
parser.add_argument("--hosts", nargs='+', default=["192.168.3.200", "192.168.3.230"], help="List of Nacos server IPs")
parser.add_argument("--data-id", required=True, help="Nacos Data ID (e.g., redis.yml)")
parser.add_argument("--group", default="DEFAULT_GROUP", help="Nacos Group (default: DEFAULT_GROUP)")
parser.add_argument("--tenant", default="dev", help="Nacos Tenant (default: dev)")
args = parser.parse_args()
config_content = None
for ip in args.hosts:
config_content = fetch_config(ip, args.data_id, args.group, args.tenant)
if config_content:
print(f"Successfully fetched config from {ip}")
break
if config_content:
print("-" * 40)
print(f"CONTENT OF {args.data_id}:")
print("-" * 40)
print(config_content)
print("-" * 40)
else:
print(f"Failed to fetch config '{args.data_id}' from all provided hosts.")
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
import sys
import argparse
import paramiko
import os
def fetch_logs(args):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
# Connect to the server
print(f"Connecting to {args.host}...")
ssh.connect(
hostname=args.host,
username=args.username,
password=args.password,
timeout=10
)
command = ""
if args.mode == "docker":
since_flag = f'--since "{args.since}"' if args.since else ""
until_flag = f'--until "{args.until}"' if args.until else ""
tail_flag = f"--tail={args.lines}" if not args.since else ""
# docker-compose logs command
command = f"docker-compose -f {args.yml_path} logs --no-log-prefix {since_flag} {until_flag} {tail_flag} {args.service}"
else:
# File mode
if args.since or args.until:
# Time-based filtering for file mode using awk
since_val = args.since if args.since else ""
until_val = args.until if args.until else ""
# Convert relative time (e.g., 10m) to absolute timestamp on server
if since_val and any(since_val.endswith(suffix) for suffix in ['s', 'm', 'h', 'd']) and since_val[:-1].isdigit():
unit_map = {'s': 'seconds', 'm': 'minutes', 'h': 'hours', 'd': 'days'}
unit = unit_map[since_val[-1]]
num = since_val[:-1]
since_expr = f"\"$(date -d '{num} {unit} ago' '+%Y-%m-%d %H:%M:%S')\""
elif since_val:
since_expr = f"'{since_val}'"
else:
since_expr = "''"
until_expr = f"'{until_val}'" if until_val else "''"
# awk script that extracts timestamp and compares
# It maintains 'in_range' state for multi-line logs (like stack traces)
awk_script = (
f"awk -v since={since_expr} -v until={until_expr} '"
"BEGIN { in_range = (since == \"\"); } "
"{ "
" if (match($0, /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/)) { "
" current_time = substr($0, RSTART, 19); "
" in_range = (since == \"\" || current_time >= since) && (until == \"\" || current_time <= until); "
" } "
" if (in_range) print $0; "
"}' " + args.log_path
)
command = awk_script
else:
# Default tail + grep for errors
command = f"tail -n {args.lines} {args.log_path} | grep -E 'Exception|Error|Caused by' -C 50"
print(f"Executing command: {command}")
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.read().decode('utf-8', errors='replace')
error = stderr.read().decode('utf-8', errors='replace')
if error and not output:
print(f"Error from server:\n{error}")
return
if not output:
print("No matching logs found.")
return
# Print a header for the output
print("-" * 40)
print(f"LOGS FROM {args.host} ({args.service if args.mode == 'docker' else args.log_path})")
print("-" * 40)
print(output)
print("-" * 40)
except Exception as e:
print(f"Failed to fetch logs: {str(e)}")
finally:
ssh.close()
def main():
parser = argparse.ArgumentParser(description="Fetch logs from a remote server via SSH.")
parser.add_argument("--host", required=True, help="Server IP or hostname")
parser.add_argument("--username", required=True, help="SSH username")
parser.add_argument("--password", help="SSH password")
parser.add_argument("--mode", choices=["docker", "file"], required=True, help="Log extraction mode")
parser.add_argument("--service", help="Docker service name (for docker mode)")
parser.add_argument("--yml-path", help="Path to docker-compose.yml (for docker mode)")
parser.add_argument("--log-path", help="Path to log file (for file mode)")
parser.add_argument("--lines", type=int, default=1000, help="Number of lines to fetch/analyze (default 1000)")
parser.add_argument("--since", help="Filter logs since this time (e.g., '10m', '2026-01-16 10:10:00')")
parser.add_argument("--until", help="Filter logs until this time (e.g., '2026-01-16 10:20:00')")
args = parser.parse_args()
if args.mode == "docker" and not (args.service and args.yml_path):
parser.error("docker mode requires --service and --yml-path")
if args.mode == "file" and not args.log_path:
parser.error("file mode requires --log-path")
fetch_logs(args)
if __name__ == "__main__":
main()