wispbyte.com薅羊毛记

有了Codex的加持,也渐渐得疯狂起来了,本身运维就是全干,对系统的各个点把控就比较精准 这次准备薅羊毛的是 wispbyte.com , 是欧洲的一个厂家 它可以免费建立一个server,准确的来说是一个容器,并且暴露一个端口出来 那先Create Server,Server Name 和 Server Description就都写 god01 好了 Server Type当然选Free Plan的,运行环境就选NodeJS 选完就按最下面的创建即可。 建好了那就去到 server的manage管理界面,下面有个Startup: 分析那句命令,意思很简单,如果配了git,就去拉代码,如果有存在npm的package.json,就先npm install安装,最后,运行index.js 下面是Docker Image,本来以为是可以随便引用别处的镜像,结果是不能,只能选固定的,那就选不太激进的 nodejs_22 ,保存好 然后就什么都不用动了 然后去到Files选项,缺省路径是 /hoe/container/ 上面图是已经弄好的,如果是新服务器,是空无一物的 我们只需要准备5个文件和一个域名: tls.crt 证书文件,用let’s encrypt申请 tls.key 密钥文件,用let’s encrypt申请 index.html 用来装饰的环保单页面,如果不加,就会显示hello world,太假了,可以让 gemini 给你生成一个,代码如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Green Network - Protect Earth</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } :root { --primary: #2e7d32; --secondary: #4caf50; --accent: #8bc34a; --light: #e8f5e9; --dark: #1b5e20; --text: #333333; --white: #ffffff; } body { background-color: var(--light); color: var(--text); line-height: 1.7; /* Slightly increased line height for readability */ font-size: 16px; } header { background: rgba(46, 125, 50, 0.95); /* Slightly transparent primary color */ color: var(--white); padding: 1rem 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Enhanced shadow */ } .container { width: 90%; max-width: 1200px; margin: 0 auto; } .nav-container { display: flex; justify-content: space-between; align-items: center; } .logo { display: flex; align-items: center; gap: 10px; font-size: 1.8rem; font-weight: 700; } .logo i { font-size: 2rem; } nav ul { display: flex; list-style: none; } nav ul li { margin-left: 2rem; } nav ul li a { color: var(--white); text-decoration: none; font-weight: 500; transition: all 0.3s ease; padding: 0.5rem 0; position: relative; } nav ul li a:hover { color: var(--accent); } nav ul li a::after { content: ''; position: absolute; bottom: 0; left: 0; width: 0; height: 2px; background-color: var(--accent); transition: width 0.3s ease; } nav ul li a:hover::after { width: 100%; } .hero { background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), url('https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2073&q=80'); background-size: cover; background-position: center; height: 80vh; display: flex; align-items: center; text-align: center; color: var(--white); } .hero-content { max-width: 800px; margin: 0 auto; } .hero h1 { font-size: 3.5rem; margin-bottom: 1rem; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); } .hero p { font-size: 1.3rem; margin-bottom: 2rem; } .btn { display: inline-block; background-color: var(--accent); color: var(--white); padding: 0.9rem 2.2rem; /* Slightly larger padding */ border-radius: 8px; /* More modern rounded corners */ text-decoration: none; font-weight: 600; transition: all 0.3s ease; border: none; cursor: pointer; box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1); /* Slightly enhanced shadow */ } .btn:hover { background-color: var(--primary); transform: translateY(-5px); /* More pronounced lift effect */ box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); /* Stronger hover shadow */ } section { padding: 5rem 0; } .section-title { text-align: center; margin-bottom: 3rem; } .section-title h2 { font-size: 2.5rem; color: var(--primary); margin-bottom: 1rem; position: relative; display: inline-block; } .section-title h2::after { content: ''; position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%); width: 90px; /* Slightly wider underline */ height: 5px; /* Thicker underline */ background-color: var(--accent); border-radius: 3px; } .section-title p { color: var(--text); max-width: 700px; margin: 0 auto; font-size: 1.1rem; } .about-content { display: grid; grid-template-columns: 1fr 1fr; gap: 3rem; align-items: center; } .about-img { border-radius: 12px; /* More rounded */ overflow: hidden; box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15); /* Enhanced shadow */ } .about-img img { width: 100%; height: auto; display: block; transition: transform 0.5s ease; } .about-img:hover img { transform: scale(1.08); /* More pronounced zoom */ } .about-text h3 { font-size: 2rem; margin-bottom: 1.5rem; color: var(--dark); } .about-text p { margin-bottom: 1.5rem; font-size: 1.05rem; } .stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2rem; margin-top: 4rem; /* More space */ } .stat-item { text-align: center; padding: 2.5rem 1.5rem; /* More padding */ background-color: var(--white); border-radius: 12px; /* More rounded */ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */ transition: transform 0.3s ease, box-shadow 0.3s ease; } .stat-item:hover { transform: translateY(-12px); /* More pronounced lift */ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */ } .stat-item h3 { font-size: 3rem; /* Larger numbers */ color: var(--dark); margin-bottom: 0.8rem; } .stat-item p { color: var(--text); font-size: 1.1rem; } .initiatives { background-color: var(--white); } .initiatives-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2.5rem; } .initiative-card { background-color: var(--light); border-radius: 12px; /* More rounded */ overflow: hidden; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */ transition: transform 0.3s ease, box-shadow 0.3s ease; } .initiative-card:hover { transform: translateY(-12px); /* More pronounced lift */ box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */ } .initiative-img { height: 220px; /* Slightly taller images */ overflow: hidden; } .initiative-img img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.5s ease; } .initiative-card:hover .initiative-img img { transform: scale(1.15); /* More pronounced zoom */ } .initiative-content { padding: 2rem; } .initiative-content h3 { font-size: 1.6rem; margin-bottom: 1rem; color: var(--dark); } .initiative-content p { margin-bottom: 1.8rem; font-size: 1.05rem; } .cta { background: linear-gradient(to right, var(--primary), var(--secondary)); color: var(--white); text-align: center; padding: 6rem 0; /* More padding */ } .cta h2 { font-size: 3rem; /* Larger heading */ margin-bottom: 1.8rem; } .cta p { font-size: 1.3rem; margin-bottom: 2.5rem; max-width: 700px; margin-left: auto; margin-right: auto; } .cta .btn { background-color: var(--white); color: var(--primary); } .cta .btn:hover { background-color: var(--accent); color: var(--white); } footer { background-color: var(--dark); color: var(--white); padding: 4rem 0 2rem; /* More padding */ } .footer-content { display: grid; grid-template-columns: repeat(4, 1fr); gap: 2.5rem; /* More gap */ margin-bottom: 2.5rem; } .footer-column h3 { font-size: 1.4rem; margin-bottom: 1.8rem; position: relative; padding-bottom: 12px; } .footer-column h3::after { content: ''; position: absolute; bottom: 0; left: 0; width: 50px; /* Wider underline */ height: 3px; /* Thicker underline */ background-color: var(--accent); } .footer-column p { margin-bottom: 1.2rem; } .footer-links { list-style: none; } .footer-links li { margin-bottom: 1rem; } .footer-links li a { color: var(--light); text-decoration: none; transition: all 0.3s ease; } .footer-links li a:hover { color: var(--accent); padding-left: 8px; /* More pronounced slide effect */ } .social-links { display: flex; gap: 1.2rem; margin-top: 1.5rem; } .social-links a { display: inline-flex; align-items: center; justify-content: center; width: 50px; /* Larger social buttons */ height: 50px; background-color: rgba(255, 255, 255, 0.15); /* Slightly more visible background */ border-radius: 8px; /* Square with rounded corners */ color: var(--white); text-decoration: none; font-weight: 600; transition: all 0.3s ease; font-size: 0.9rem; } .social-links a:hover { background-color: var(--accent); transform: translateY(-7px); /* More pronounced lift */ } .copyright { text-align: center; padding-top: 2.5rem; border-top: 1px solid rgba(255, 255, 255, 0.15); } .mobile-menu { display: none; font-size: 1.5rem; cursor: pointer; padding: 0.5rem 1rem; border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 5px; transition: all 0.3s ease; } .mobile-menu:hover { background-color: rgba(255, 255, 255, 0.1); } @media (max-width: 992px) { .about-content { grid-template-columns: 1fr; } .initiatives-grid { grid-template-columns: repeat(2, 1fr); } .footer-content { grid-template-columns: repeat(2, 1fr); } .stats { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { .hero h1 { font-size: 2.8rem; /* Adjusted for better readability on smaller screens */ } .hero p { font-size: 1.2rem; /* Adjusted for better readability on smaller screens */ } nav ul { display: none; position: absolute; top: 100%; left: 0; width: 100%; background-color: var(--primary); flex-direction: column; padding: 1rem 0; text-align: center; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); } nav ul.active { display: flex; } nav ul li { margin: 0; padding: 0.8rem 0; } .mobile-menu { display: block; font-size: 1.2rem; /* Slightly smaller for mobile */ padding: 0.4rem 0.8rem; } .initiatives-grid { grid-template-columns: 1fr; } .footer-content { grid-template-columns: 1fr; } .stats { grid-template-columns: 1fr; } .social-links a { width: 45px; /* Slightly smaller social buttons */ height: 45px; font-size: 0.85rem; } } /* Animation for stats counter */ .counter { transition: all 0.5s ease; } </style> </head> <body> <!-- Header --> <header> <div class="container nav-container"> <div class="logo"> <span>Green Network</span> </div> <nav> <div class="mobile-menu"> Menu </div> <ul> <li><a href="#home">Home</a></li> <li><a href="#about">About</a></li> <li><a href="#initiatives">Initiatives</a></li> <li><a href="#get-involved">Get Involved</a></li> <li><a href="#contact">Contact</a></li> </ul> </nav> </div> </header> <!-- Hero Section --> <section class="hero" id="home"> <div class="container hero-content"> <h1>Protect Our Planet, Preserve Our Future</h1> <p>Join the global movement to create a sustainable world through conservation, education, and community action.</p> <a href="#get-involved" class="btn">Join Our Network</a> </div> </section> <!-- About Section --> <section id="about"> <div class="container"> <div class="section-title"> <h2>About Green Network</h2> <p>We are a global community dedicated to environmental protection and sustainable development.</p> </div> <div class="about-content"> <div class="about-img"> <img src="https://images.unsplash.com/photo-1507591064344-4c6ce005b128?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80" alt="Team planting trees"> </div> <div class="about-text"> <h3>Our Mission</h3> <p>Green Network is committed to creating a sustainable future by protecting natural ecosystems, promoting renewable energy, and empowering communities to take environmental action.</p> <p>Founded in 2010, we've grown from a small grassroots organization to an international network with over 50,000 members across 120 countries.</p> <p>Our approach combines scientific research, community engagement, and policy advocacy to address the most pressing environmental challenges of our time.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="stats"> <div class="stat-item"> <h3 class="counter" data-target="2500000">0</h3> <p>Trees Planted</p> </div> <div class="stat-item"> <h3 class="counter" data-target="50000">0</h3> <p>Active Members</p> </div> <div class="stat-item"> <h3 class="counter" data-target="120">0</h3> <p>Countries Reached</p> </div> <div class="stat-item"> <h3 class="counter" data-target="1500">0</h3> <p>Cleanup Projects</p> </div> </div> </div> </section> <!-- Initiatives Section --> <section class="initiatives" id="initiatives"> <div class="container"> <div class="section-title"> <h2>Our Initiatives</h2> <p>Discover the key programs and projects we're implementing to protect our planet.</p> </div> <div class="initiatives-grid"> <div class="initiative-card"> <div class="initiative-img"> <img src="https://dialogue.earth/content/uploads/2021/04/lessons-from-the-rush-to-reforest-china-dialogue-2400x1599.jpg" alt="Reforestation"> </div> <div class="initiative-content"> <h3>Global Reforestation</h3> <p>Planting millions of trees worldwide to restore ecosystems, combat climate change, and protect biodiversity.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="initiative-card"> <div class="initiative-img"> <img src="https://images.unsplash.com/photo-1621451537084-482c73073a0f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80" alt="Ocean Conservation"> </div> <div class="initiative-content"> <h3>Ocean Conservation</h3> <p>Protecting marine ecosystems, reducing plastic pollution, and promoting sustainable fishing practices.</p> <a href="#" class="btn">Learn More</a> </div> </div> <div class="initiative-card"> <div class="initiative-img"> <img src="https://images.unsplash.com/photo-1508514177221-188b1cf16e9d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2072&q=80" alt="Renewable Energy"> </div> <div class="initiative-content"> <h3>Renewable Energy</h3> <p>Promoting solar, wind, and other clean energy sources to reduce dependence on fossil fuels.</p> <a href="#" class="btn">Learn More</a> </div> </div> </div> </div> </section> <!-- CTA Section --> <section class="cta" id="get-involved"> <div class="container"> <h2>Join Our Global Movement</h2> <p>Together, we can create a sustainable future for generations to come. Every action counts, no matter how small.</p> <a href="#" class="btn">Become a Member</a> </div> </section> <!-- Footer --> <footer id="contact"> <div class="container"> <div class="footer-content"> <div class="footer-column"> <h3>Green Network</h3> <p>We are dedicated to protecting our planet through collaborative action, education, and sustainable solutions.</p> <div class="social-links"> <a href="#">Facebook</a> <a href="#">Twitter</a> <a href="#">Instagram</a> <a href="#">LinkedIn</a> </div> </div> <div class="footer-column"> <h3>Quick Links</h3> <ul class="footer-links"> <li><a href="#home">Home</a></li> <li><a href="#about">About Us</a></li> <li><a href="#initiatives">Initiatives</a></li> <li><a href="#get-involved">Get Involved</a></li> <li><a href="#contact">Contact</a></li> </ul> </div> <div class="footer-column"> <h3>Our Programs</h3> <ul class="footer-links"> <li><a href="#">Reforestation</a></li> <li><a href="#">Ocean Cleanup</a></li> <li><a href="#">Wildlife Protection</a></li> <li><a href="#">Climate Education</a></li> <li><a href="#">Sustainable Agriculture</a></li> </ul> </div> <div class="footer-column"> <h3>Contact Us</h3> <p>Copper Creek Drive, New York</p> <p>+1 (312) 171-0771</p> <p>support@greennetwork.org</p> </div> </div> <div class="copyright"> <p>&copy; 2025 Green Network. All rights reserved.</p> </div> </div> </footer> <script> // Mobile Menu Toggle document.querySelector('.mobile-menu').addEventListener('click', function() { document.querySelector('nav ul').classList.toggle('active'); }); // Counter Animation function animateCounters() { const counters = document.querySelectorAll('.counter'); const speed = 200; // The lower the slower counters.forEach(counter => { const target = +counter.getAttribute('data-target'); const count = +counter.innerText; const inc = target / speed; if(count < target) { counter.innerText = Math.ceil(count + inc); setTimeout(animateCounters, 1); } else { counter.innerText = target; } }); } // Initialize counters when page loads window.addEventListener('load', animateCounters); // Smooth scrolling for navigation links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); if(targetId === '#') return; const targetElement = document.querySelector(targetId); if(targetElement) { window.scrollTo({ top: targetElement.offsetTop - 80, behavior: 'smooth' }); document.querySelector('nav ul').classList.remove('active'); } }); }); </script> </body> </html> package.json 文件,index.js 运行时需要依赖的安装包文件,安装了2个包,axios和ws ...

2026年1月1日

Claude code的skills编写

最近天天都在学习claude code,Skills == 工具箱(技能库) 我们可以把skills看成是一个个的小程序,这些程序有入口,有参数,有限制。 那通过一篇md文件,来引导claude来智能的传参调用你的程序,这样技能箱就越来越丰富。 一、我们首先准备这个可执行 python 脚本 这个是通过CLIProxyAPI的反代,把antigravity的gemini3模型给抽出来,然后生图 看最下面,有一堆参数 #!/usr/bin/env python3 # ============================================================ # Configuration - Edit these values before use # 配置项 - 使用前请修改以下值 # ============================================================ API_BASE_URL = "http://127.0.0.1:8317" # API server address / API 服务器地址 API_KEY = "key" # Your API key / 你的 API 密钥 MODEL = "gemini-3-pro-image-preview" # Model name / 模型名称 # ============================================================ import argparse import base64 import json import os import sys from datetime import datetime from pathlib import Path from urllib import request from mimetypes import guess_type try: from google import genai from google.genai import types except ImportError: genai = None types = None VALID_ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"] VALID_RESOLUTIONS = ["1K", "2K", "4K"] def _resolve_output_dir(output_dir: str, mkdir: bool) -> str: if os.path.isdir(output_dir): return output_dir if mkdir: os.makedirs(output_dir, exist_ok=True) return output_dir print(f"Warning: Output directory not found: {output_dir}. Saving to current directory.") return "." def _build_filename(output_name: str | None, timestamp: str, index: int, default_ext: str) -> str: if output_name: root, ext = os.path.splitext(output_name) if not ext: ext = default_ext suffix = f"_{index}" if index > 0 else "" return f"{root}{suffix}{ext}" return f"generated_{timestamp}_{index}{default_ext}" def _load_image_inline(image_path: str) -> dict: mime_type, _ = guess_type(image_path) if mime_type is None: mime_type = "image/png" data = Path(image_path).read_bytes() return {"inlineData": {"mimeType": mime_type, "data": base64.b64encode(data).decode("ascii")}} def _post_json(url: str, body: dict) -> dict: data = json.dumps(body).encode("utf-8") req = request.Request( url, data=data, headers={"Content-Type": "application/json", "x-goog-api-key": API_KEY}, ) resp = request.urlopen(req).read() return json.loads(resp) def _save_images(parts, output_dir: str, select: str, output_name: str | None, mkdir: bool) -> list[str]: imgs = [p["inlineData"] for p in parts if "inlineData" in p] if not imgs: return [] if select == "first": imgs = imgs[:1] elif select == "last": imgs = imgs[-1:] elif select == "max_res": imgs = imgs[-1:] ts = datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = _resolve_output_dir(output_dir, mkdir) saved = [] for i, img in enumerate(imgs): data = base64.b64decode(img["data"]) ext = "jpg" if img.get("mimeType", "").endswith("jpeg") else "png" filename = _build_filename(output_name, ts, i, f".{ext}") path = Path(output_dir) / filename path.write_bytes(data) saved.append(str(path)) return saved def _sdk_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None, output_name: str | None, mkdir: bool, select: str) -> list[str]: if genai is None or types is None: print("Error: google-genai is not installed.") return [] client_kwargs = {"api_key": API_KEY} if API_BASE_URL: client_kwargs["http_options"] = {"base_url": API_BASE_URL} client = genai.Client(**client_kwargs) if input_image: if not Path(input_image).exists(): print(f"Error: Input image not found: {input_image}") return [] with open(input_image, "rb") as f: image_bytes = f.read() mime_type, _ = guess_type(input_image) if mime_type is None: mime_type = "image/png" image_part = types.Part.from_bytes(data=image_bytes, mime_type=mime_type) contents = [prompt, image_part] else: contents = prompt try: response = client.models.generate_content( model=MODEL, contents=contents, config=types.GenerateContentConfig( image_config=types.ImageConfig( aspectRatio=aspect_ratio, imageSize=resolution, ) ), ) except Exception as exc: print(f"API Error: {exc}") return [] saved = [] ts = datetime.now().strftime("%Y%m%d_%H%M%S") output_dir = _resolve_output_dir(output_dir, mkdir) image_parts = [] for part in response.parts: if part.text is not None: print(f"Response text: {part.text}") elif image := part.as_image(): image_parts.append(image) if not image_parts: return [] if select == "first": image_parts = image_parts[:1] elif select == "last": image_parts = image_parts[-1:] elif select == "max_res": image_parts = [max(image_parts, key=lambda x: len(x.image_bytes))] for i, image in enumerate(image_parts): filename = _build_filename(output_name, ts, i, ".png") path = Path(output_dir) / filename image.save(path) saved.append(str(path)) return saved def _http_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None, select: str, output_name: str | None, mkdir: bool) -> list[str]: if input_image and not Path(input_image).exists(): print(f"Error: Input image not found: {input_image}") return [] url = f"{API_BASE_URL.rstrip('/')}/v1beta/models/{MODEL}:generateContent" parts = [{"text": prompt}] if input_image: parts.append(_load_image_inline(input_image)) body = { "contents": [{"parts": parts}], "generationConfig": { "responseModalities": ["TEXT", "IMAGE"], "imageConfig": {"aspectRatio": aspect_ratio, "imageSize": resolution}, }, } try: resp = _post_json(url, body) except Exception as exc: print(f"API Error: {exc}") return [] if "candidates" not in resp: print(f"Unexpected response: {resp}") return [] parts_out = resp["candidates"][0]["content"]["parts"] return _save_images(parts_out, output_dir, select, output_name, mkdir) def main(): parser = argparse.ArgumentParser(description="Generate images via SDK or HTTP") parser.add_argument("prompt", nargs="*", help="Image description prompt") parser.add_argument("--mode", default="sdk", choices=["sdk", "http"]) parser.add_argument("--aspect-ratio", "-a", default="16:9", choices=VALID_ASPECT_RATIOS) parser.add_argument("--resolution", "-r", default="4K", choices=VALID_RESOLUTIONS) parser.add_argument("--output-dir", "-o", default=".") parser.add_argument("--output-name", default=None) parser.add_argument("--mkdir", action="store_true") parser.add_argument("--input-image", "-i", default=None) parser.add_argument("--select", default="all", choices=["all", "first", "last", "max_res"]) args = parser.parse_args() if not args.prompt: parser.print_help() sys.exit(1) prompt = " ".join(args.prompt) if args.mode == "sdk": saved = _sdk_generate( prompt=prompt, output_dir=args.output_dir, aspect_ratio=args.aspect_ratio, resolution=args.resolution, input_image=args.input_image, output_name=args.output_name, mkdir=args.mkdir, select=args.select, ) else: saved = _http_generate( prompt=prompt, output_dir=args.output_dir, aspect_ratio=args.aspect_ratio, resolution=args.resolution, input_image=args.input_image, select=args.select, output_name=args.output_name, mkdir=args.mkdir, ) if saved: print(f"Generated {len(saved)} image(s):") for f in saved: print(f" - {Path(f).resolve()}") else: print("No images were generated.") sys.exit(1) if __name__ == "__main__": main() 基本用法: ...

2025年12月31日

整理下AI大模型厂商和平台,能长期稳定提供免费额度的API (非公益站) - 转自Linux.do

现在AI的使用场景越来越多,公益站有时也不稳定,给大家整理一些能提供相对长期稳定大模型api的厂商和平台,作为备用或测试。 这里主要收集文本大模型,图片视频生成相关的大模型没有专门做整理。 tldr 国内大模型平台太卷了,免费额度真的很多,如果没有特殊需求,国内的api就够用了。 主力模型推荐: 阿里iflow, 字节火山引擎, 阿里 modelscope 魔搭社区。 免费vibe coding推荐: 腾讯codebuddy, 快手codeflicker, 阿里通义灵码/qwen-code 非稳定渠道 一些平台会不定期推出吸引用户的免费活动,适合用来测试和临时应急。下面列出一些,过期了的就评论下提醒我删掉。 AI Ping 20251226 限时免费: glm-4.7, minimax-m2.1, deepseek-v3.2, douban-seeddream文生图 模型限制相关说明 rpm(Requests per minute): 每分钟请求次数 rpd(Requests per day): 每天请求次数 tpm(Tokens per minute): 每分钟输入输出的token数 tpd(Tokens per day): 每天输入输出的token数 Vibe Coding 免费代码工具 国内的 ai coding 太卷了,各家都提供了很大的免费额度 腾讯云代码助手 CodeBuddy, 独立IDE 目前(20251222)免费使用 glm-4.6, deepseek-v3.1-terminus, huyuan-2.0 20251223: 免费提供最新的 glm-4.7 快手 CodeFlicker , 独立IDE 目前(20251222)免费使用 kimi-k2-0905, kat-coder-pro 阿里 通义灵码 , 独立IDE 免费不限量使用 千问系列模型,但不可更换使用其他模型 阿里 qwen-code, cli命令行 free tier ...

2025年12月31日

CLIProxyAPI反代antigravity单独走代理

用CLIProxyAPI反代了antigravity,抽出了claude的模型给claude code用 同时也接入了qwen3-code-plus模型 还接入了openrouter的模型 后期还准备接入minmax等模型 这样就出现个问题:访问 antigravity 需要代理,而访问qwen和openrouter或者其它可能并不需要 如果设置了全局代理,那访问其它2个访问就绕路了 本来想提个PR的,但是感觉不是什么大需求 所以干脆用claude改了一下,配置文件config.yaml中新增了一个配置项 antigravity_proxy: "" 这样方便了就 代码放在:GitHub - zhangrr/CLIProxyAPI: modify CLIProxyAPI claude code 的 settings.json 配置如下: { “env”: { “ANTHROPIC_BASE_URL”: “http://10.8.2.25:8317”, “ANTHROPIC_AUTH_TOKEN”: “sk-xxxxxxx”, “ANTHROPIC_DEFAULT_HAIKU_MODEL”: “qwen3-coder-plus”, “ANTHROPIC_DEFAULT_OPUS_MODEL”: “gemini-claude-sonnet-4-5-thinking”, “ANTHROPIC_DEFAULT_SONNET_MODEL”: “gemini-claude-sonnet-4-5-thinking”, “ANTHROPIC_MODEL”: “gemini-claude-sonnet-4-5-thinking” } } 请自行取用

2025年12月30日

魔搭、模力方舟、豆包免费文生图

在别人的基础之上,手搓了一个利用三个模型提供商的免费模型额度,来文生图的程序 原地址:https://github.com/lianwusuoai/img-router ,[智能图像生成网关,通过OpenAI 兼容接口,使用chat自动路由多平台 AI 进行免费绘图] 魔改的地址:http://github.com/zhangrr/image-route 亮点:免费API人人都有,不需要专门绘图网站,不需要考虑各种接口,不需要烦恼Cherry Studio/RikkaHub/newapi等客户端or平台不兼容/不支持,让文生图/图生图/图片编辑,就像和ai聊天chat一样方便。 模力方舟免费key每天100次 无审核,不要白天随便生图,涩涩警告! 魔搭免费key每天2000次 单模型500次 不可涩涩! 豆包4.0+4.5每个模型200次,每天最多恢复20次,可以设置上限防超出 不可涩涩! 那原版是只能通过API进行访问,正好有claude code的额度,那就干脆套个web的界面出来 注意: 魔力方舟注册后,生成api的key即可 魔搭是阿里的,生成api的key后,需要经过阿里的账号验证,才能使用 不要部署任何模型,阿里的账号最好里面没有绑卡,没有钱 豆包是火山引擎,开通账号生成api的key后,需要实人认证,然后还需要部署模型 拿到3个免费的API后,克隆源代码: http://github.com/zhangrr/image-route 然后把api的key填入config.js(克隆一下config.example.js) 装好deno,一句话就可以运行了: ./deno run --allow-net --allow-env --allow-write --allow-read main.ts 生了一张图,模力方舟,还可以:

2025年12月30日

Linux机器的历史命令查询和保存

由于自己实际的工作机是一台Linux,更准确的说是Debian 12 多数时间就是通过WindTerm ssh登录到这台机器上,然后开多个窗口,开始干活 那么查看历史命令就变得很重要了,需要全部记录下来,随时进行查看 如果是公司的服务器,也可以加上这个,统一把命令历史发送到统一的一台日志服务器上,进行保管和审计 Any,都很重要,记录一下: 一、编辑 /etc/profile,新增三句 export HISTTIMEFORMAT="%Y-%m-%d %H:%M " export PROMPT_COMMAND='RETRN_VAL=$?;logger -p local6.debug "$(whoami) [$$]: $(history 1 | sed "s/^[ ]*[0-9]\+[ ]*//") [$RETRN_VAL]"' export PROMPT_COMMAND="history -a;history -c;history -r;$PROMPT_COMMAND" 第一句是为了使用 history 的时候更好看一些 二三句是为了把历史命令发到日志服务器去 二、安装并设置rsyslog apt install rsyslog vi /etc/rsyslog.d/00-my-format.conf # 定义一个简洁的时间格式模板:2025-12-30 09:50 $template MyPreciseFormat,"%TIMESTAMP:1:10:date-rfc3339% %TIMESTAMP:12:16:date-rfc3339% %HOSTNAME% %syslogtag%%msg%\n" # 将这个模板设置为所有日志文件的默认格式 $ActionFileDefaultTemplate MyPreciseFormat vi /etc/rsyslog.conf ... auth,authpriv.* /var/log/auth.log cron.* -/var/log/cron.log kern.* -/var/log/kern.log mail.* -/var/log/mail.log user.* -/var/log/user.log local6.* /var/log/commands.log ... systemctl enable rsyslog systemctl restart rsyslog 如上,就会把命令都发送到 /var/log/commands.log ...

2025年12月30日

关于学习,关于AI

Dev.to 上面有一篇深度好文,最近在孜孜不倦的学习AI,感觉什么Cluaude code、Codex、Gemini,各种模型纷至沓来,忙乱且自顾不暇。感觉很慌乱,看到了这篇好文,顿时安静下来。慢就是快,慢慢来! Coding Without Pressure: How Slowing Down Helped Me Learn Faster ​ For a long time, I thought learning to code had to feel intense. Daily goals. Long hours. Constant progress. If I wasn’t exhausted, I felt like I wasn’t doing enough. So I pushed harder. More tutorials. More projects. More pressure. And somehow… I learned less. It took me a while to realize this simple truth… I wasn’t failing because I was slow; I was failing because I wouldn’t let myself slow down. ...

2025年12月30日

AWS的迁移替换

本篇是叙述文,没有技术含量,纯纯的记录一下 那在25年9月份的时候,把AWS的整个架构给平移到VPS的机房了 为什么呢?AWS的费用实在是太贵了 各种服务都是要钱的,什么Mysql的proxy,跟实例一个价格了;还有什么Serurity hub,Cloudwatch等等等等 用到了就得交钱,出故障的时候,还得买客服支持服务,各种奇技淫巧 看日志发告警用Cloudwatch是最快最便捷的,但是贵啊,最佳实践是仍到S3桶里,用 Athena 分析 每个月都要关注saveing plan,看看超了没有,实在是忍无可忍 而且被迫升级,用的服务各种阉割 redis服务只支持一个库,被迫升级到valkey, 结果valkey不支持redis-exporter,被逼无奈只好用社区的redis.io alermanager不支持webhook,只能发到SNS 无语了,而且最佳实践太多,每次都得研究半天什么才是最合适自己得 写个Terrfaform,代码总共2千行,有1000行是IAM的权限配置 终于终于挪了 那各种服务必须有相应的替代产品,那总结一下: Loadbalance + Firewall:就用OPNSense替代 Kubernetes: 用kuberspray手搓 Redis: 自建redis Postgres和Mysql: 手搓,用pgbackrest备份。MySQL每天fullbackup S3: 用seeweedfs替代,完美替换,支持各种IAM替代,静态化用Caddy来顶替 cloudfront:其它家CDN Elasticsearch: 自建,取消证书 MongoDB: 自建 Kafka:自建,kraft换掉zookeeper flink:自建 milvus:自建

2025年12月17日

OPNSense配置配置wireguard

居然花了一晚上和一整天的时间来配置这个,真的是大出所料,在最浅显的地方栽了最大的跟头 因为之前配置的时候都太痛快了,5分钟,然后放路由,就ok了,结果这次就结结实实翻车了! 先说最大的原因:墙,无视墙的存在,必然被墙反噬,本来简单的事情也变得复杂。 注意事项,普通配其实很简单,但是出乎意料的还有MTU等着你,因为VPS被强制改了MTU,所以这里还有一个坑,普通的不会遇到这个情况。 记录一下过程: 首先,OPNSense老版本的需要安装插件os-wireguard,但新版本已经内置wireguard了,完全不用安装 左边栏看一下,有就OK,不用安装 然后我们先去WireGuard的Instances,初始是空白的,那就按+号添加一个新的实例 需要填写以下几项 Name:WG-LC Instance是0,表示这个实例会是wg0这个网卡 按齿轮Gen出sever的 Public key和Private key LIsten port:51821 采用非标端口,是UDP类型的端口 Tunnel address:表明wg0网卡所在的网段,10.100.100.1是服务器的地址 然后填完点击Save就OK了 返回后,选中Enable WireGuard,然后Apply,就会启动服务了。 接着去gen客户端配置: 到 Peer generator,填入以下内容 Name:客户端名称,起个ZRR,好认 Address:会自动按序列生成,这里是10.100.100.2/32 Allowed IPs:这里非常关键,一定不能填0.0.0.0,那相当于所有路由都走这个wg隧道了,所以一定要指定自己要走的路由,首先是wg0的网段10.100.100.0/24,然后是内网的192.168.99.0/24 然后一定要把Config的配置给拷贝出来备用 最后点击 Store and generate nexe的按钮,不要点Apply那个 然后就OK了,回到 Peers 页看看,多了一个ZRR的对端,就没问题 继续,需要配置接口和防火墙规则 左边栏,Interfaces –> Assignments 会多出一个interface,wg0的,Description写大写的WG0,必须是这个,不要起其它的名字,WG0 –> wg0相呼应对照,不要乱写 点击Add 会变成三个并排,如果加多个实例,就WG1、WG2这么排下去,一目了然 接着去WG0的interfaces 选中: Enable 激活这个interface Lock 阻止这个interface被误删 其它都保持缺省 然后点击Save,然后Apply changes,让配置生效,就可以了 去Firewall,Rules, WG0,会看到有13条继承的规则,我们新加一条 ...

2025年12月17日

Ai时代的Blog换脸与字体压缩

不得不感叹,ai时代的强大,博主的本职是一个运维,对前端和CSS懂的不多,用的软件是Hugo,其实对这个也一窍不通,就是选了一个自己看得顺眼的theme,然后就是用来写文章,期间也尝试去做些改变,均已失败告终,主要是对hugo、theme、css不清楚,也不知道怎么改。 现在有了AI的加持,这个一直用了好多年的blog的样子也看烦了,准备换掉。 旧的界面: 新的界面: 用了gemini,花了一天时间,主要是嫌弃底部的前一页、后一页看起来永无止境,于是让ai搓了一个底部导航条。也定制了css。确实惊艳一枪 但是过程中吧,用到了一个开源的字体:OPPOSans-Regular.ttf ,OPPO手机的字体,这个文件居然有9兆之巨 完犊子了,加载字体都要花半天时间,这也太恐怖了,于是搜优化方案 那就是压缩字体,把博客中出现的所有字扫描一遍,然后生成小字体文件,这样就好了 那就要用到 glyphhanger 了 注意:这个软件用起来及其麻烦,一定要在Windows下装,Linux下简直是垃圾成堆 本体是一个nodejs的软件,用到了python的fonttools,还装了puppyteer进行网页的抓取,简直是杂烩大集合 # 要压缩成woff2,所以要装python的2个包 npm install -g glyphhanger pip install fonttools pip install brotli #打开hugo的预览,http://localhost:1313/ hugo serve # 当前目录下的OPPOSans-Regular.ttf,生成压缩过的字体文件,格式是woff2 glyphhanger --subset OPPOSans-Regular.ttf --formats=woff2 --output ./ http://localhost:1313/posts/ ttf的完整字体经过压缩成woff2,从9兆变成了4.52兆,然后扫描网页后又压缩到了最终的75.5KB. 这会生成一个OPPOSans-Regular-subset.woff2的新字体文件,75.5KB 没完呢,我们还要在CSS里指定字体范围 # 压缩字体文件,同时生成CSS样式 glyphhanger --css --subset fonts/OPPOSans-Regular.woff2 --formats=woff2 --output newfonts http://localhost:1313/posts/ 会生成CSS文件,我们把它放进定制的custom.css中即可 @font-face { font-family: OPPO Sans; src: local("OPPO Sans"), url(OPPOSans-Regular-subset.woff2) format("woff2"); unicode-range: U+A,U+20-3E,U+40-7D,U+A9,U+B7,U+BB,U+2013,U+25BA,U+2705,U+3001,U+3002,U+4E00,U+4E09-4E0B,U+4E0D,U+4E14,U+4E1A,U+4E1C,U+4E24,U+4E2A,U+4E2D,U+4E34,U+4E3A,U+4E3B,U+4E48,U+4E49,U+4E4B,U+4E4E,U+4E50,U+4E5F,U+4E66,U+4E71,U+4E86,U+4E89,U+4E8B,U+4E8C,U+4E8E,U+4E94,U+4E9B,U+4EA6,U+4EA7,U+4EAB,U+4EBA,U+4ECA,U+4ECE,U+4ED3,U+4EE3-4EE5,U+4EEC,U+4EF6,U+4EFB,U+4EFD,U+4F01,U+4F1A,U+4F20,U+4F3C,U+4F46,U+4F53,U+4F55,U+4F59,U+4F5C,U+4F60,U+4F73,U+4F7F,U+4FBF,U+4FDD,U+4FE1,U+4FEE,U+5019,U+503C,U+5047,U+504F,U+505A,U+50CF,U+513F,U+5143,U+5148,U+5165,U+5168,U+516B,U+516C,U+5171,U+5173,U+5176,U+5177,U+517C,U+5185,U+518D,U+5197,U+5199,U+51B3,U+51B5,U+51C0,U+51C6,U+51FA,U+51FB,U+51FD,U+5206,U+5212,U+5219-521B,U+521D,U+5220,U+522B,U+5230,U+524D,U+526F,U+529E-52A1,U+52A8,U+52B2,U+5305,U+5316,U+5355,U+535A,U+5360,U+5361,U+5373,U+5386,U+53BB,U+53C2,U+53C8,U+53CA,U+53D1,U+53D6,U+53D8,U+53E3,U+53E4,U+53EA,U+53EF,U+53F0,U+53F7,U+53F8,U+5404,U+540C-540E,U+5411,U+5426,U+5427,U+542F,U+544A,U+5462,U+547D,U+548C,U+54EA,U+554A,U+5565,U+5668,U+56DB,U+56DE,U+56E0,U+56E7,U+56FD,U+56FE,U+5728,U+5730,U+573A,U+5740,U+5757,U+578B,U+57DF,U+585E,U+5883,U+589E,U+5904,U+5907,U+590D,U+5916,U+591A,U+5927,U+5929,U+592A,U+5931,U+5948,U+597D,U+5982,U+59CB,U+5B50,U+5B57,U+5B58,U+5B83,U+5B89,U+5B8C,U+5B98,U+5B9A,U+5B9E,U+5BA2,U+5BB6,U+5BB9,U+5BBD,U+5BC6,U+5BCC,U+5BF9,U+5BFC,U+5C01,U+5C06,U+5C31,U+5C3D,U+5C45,U+5DDE,U+5DF1,U+5DF2,U+5E02,U+5E03,U+5E38,U+5E72-5E74,U+5E76,U+5E78,U+5E7F,U+5E8F,U+5E93,U+5E94,U+5EFA,U+5F00,U+5F04,U+5F0F,U+5F20,U+5F31,U+5F39,U+5F52,U+5F53,U+5F55,U+5F85,U+5F88,U+5F97,U+5FAA,U+5FC3,U+5FC5,U+5FD7,U+5FEB,U+6000,U+6001,U+600E,U+601D,U+6027,U+603B,U+6062,U+606F,U+6089,U+60A8,U+60C5,U+60F3,U+6109,U+610F,U+613F,U+6210-6212,U+6216,U+6237,U+623F,U+6240,U+624B,U+624D,U+6253,U+6254,U+6258,U+6267,U+6269,U+627E,U+6280,U+628A,U+62A5,U+62C9,U+62DF,U+62E9,U+62EC,U+62FF,U+6301,U+6307,U+6309,U+636E,U+6377,U+6389,U+6392,U+63A5,U+63A8,U+641C,U+642D,U+6446,U+6447,U+64CD,U+652F,U+6539,U+653E,U+6545,U+6570,U+6574,U+6587,U+65AD,U+65B0,U+65B9,U+65E0,U+65E5,U+65E9,U+65F6,U+660E,U+6613,U+662F,U+663E,U+666F,U+66B4,U+66F4,U+6700,U+6708,U+6709,U+670D,U+671F,U+672A,U+672C,U+672F,U+673A,U+6743,U+6765,U+677E,U+677F,U+6790,U+679C,U+67D0,U+67E5,U+6807,U+6837-6839,U+683C,U+6848,U+6863,U+68AD,U+68C0,U+6A21,U+6B21,U+6B4C,U+6B62,U+6B63,U+6B65,U+6B7B,U+6BB5,U+6BCF,U+6BD4,U+6C42,U+6C89,U+6CA1,U+6CD5,U+6CE8,U+6D3B,U+6D4B,U+6D88,U+6DFB,U+6E05,U+6E90,U+6EE5,U+6F0F,U+706B,U+70B9,U+70C2,U+70E6,U+7130,U+7136,U+7167,U+719F,U+723D,U+7248,U+7279,U+72B6,U+72EC,U+73A9,U+73AF,U+73B0,U+7406,U+751F,U+7528,U+7531,U+754C,U+7591,U+7684,U+76D6,U+76EE,U+76F4,U+76F8,U+7701,U+770B,U+771F,U+77E5,U+77ED,U+7801,U+7814,U+786E,U+793A,U+79BB,U+79CD,U+79D2,U+79DF,U+79F0,U+79FB,U+7A00,U+7A0B,U+7A76,U+7A7A,U+7ACB,U+7AD9,U+7ADE-7AE0,U+7AEF,U+7B2C,U+7B49,U+7B54,U+7B7E,U+7B80,U+7B97,U+7BA1,U+7C7B,U+7CFB,U+7D22,U+7E41,U+7EA7,U+7EAF,U+7EC6,U+7ECF,U+7ED3,U+7ED9,U+7EDC,U+7EDF,U+7EED,U+7F3A,U+7F51,U+7F6E,U+7F72,U+7FA4,U+8003,U+8005,U+800C,U+800D,U+8017,U+804C,U+8054,U+80AF,U+80CC,U+80FD,U+8106,U+8111,U+811A,U+817E,U+81EA,U+81F4,U+8272,U+8282,U+8350,U+83B7,U+83DC,U+85CF,U+8651,U+865A,U+884C,U+88AB,U+88C2,U+88C5,U+88D5,U+897F,U+8981,U+8986,U+89C9,U+89D2,U+89E3,U+8A00,U+8B66,U+8BA1,U+8BA2,U+8BAF,U+8BB2,U+8BBA,U+8BBE,U+8BBF,U+8BC1,U+8BD5,U+8BDD,U+8BE2,U+8BE5,U+8BE6,U+8BED,U+8BEF,U+8BF4,U+8BF7,U+8C03,U+8C37,U+8C61,U+8D25,U+8D44,U+8D70,U+8D77,U+8DD1,U+8DDF,U+8DEF,U+8DF3,U+8DF5,U+8F7B,U+8F7D,U+8F83,U+8F93,U+8FC1,U+8FC7,U+8FD0,U+8FD4,U+8FD8,U+8FD9,U+8FDB,U+8FDE,U+9000-9002,U+9009,U+901A,U+903C,U+9047,U+904D,U+9053,U+9075,U+90A3,U+90E8,U+90FD,U+914D,U+91CA,U+91CC,U+91CD,U+91CF,U+94FE,U+9501,U+9519,U+952E,U+955C,U+957F,U+95EE,U+95F4,U+9632,U+963B,U+9645,U+964B,U+9650,U+9664,U+968F,U+9690,U+969C,U+96BE,U+96C6,U+9700,U+975E,U+9762,U+9875,U+9876,U+987B,U+9884,U+9891,U+9898,U+9996,U+9AA4,U+9AD8,U+9EBB,U+FF01,U+FF0C,U+FF1A,U+FF1F; font-style: normal; font-display: swap; font-weight: 300 700; } 把压缩后的字体和CSS再次提交后博客的速度就变得飞快了。 ...

2025年12月13日