包含关键字 typecho 的文章

在 vscode 系编辑器中使用 claude code 插件时候,可能会遇到强制登陆的问题:

Claude Code can be used with your Claude subscription or billed
based on API usage through your Console account.

How do you want to log in

这个时候不要慌,你的配置可能没有问题。

打开编辑器设置,找到插件中的 claude code,打开这个选项

再次打开 claude code 就会发现没有登陆提示了


📌 转载信息
转载时间:
2026/1/14 18:14:14

分享下 window10 的包管理工具 winget ,类似 apt、brew

# 关键字搜索
winget find 输入法
# ID 安装,可以多个
winget install Tencent.WeType astral-sh.uv
# 检查更新
winget update
# 更新,可以多个
winget update Tencent.WeType astral-sh.uv
# 本地列表
winget list [可选关键字]
# 查看软件信息
winget show Tencent.WeType

注意默认会安装在系统盘

部分软件的小版本可能不是最新,取决于软件官方更新的速度吧

最好有魔法,部分电脑不知道为什么没有魔法不行,但我有的电脑却可以

最大的好处就是:不用到处找安装包了

我一般的小软件都优先在 winget 搜索安装

如:uv git volta 等工具

不想安装到系统盘的,也可以通过 winget 查找下载连接,手动安装到其他盘


📌 转载信息
原作者:
zhedream
转载时间:
2026/1/14 18:12:47

claude 的 chrome 插件出来有一段时间了,claude code 说可以联动控制 chrome,但我在 wsl 里试了下,直接 “翻车”~(报错:Extension: not detected),无法连接 windows 的 chrome。前两天逛 github 看到个 issue,按大神的法子试了下,终于搞定了~现在 wsl 也能丝滑控制 windows chrome 啦。把踩坑和解决步骤整理出来,供有需要的佬参考~

  1. 先决条件:

    • windows 下 已安装 google chrome 浏览器
    • chrome 已安装 claude in chrome extension 插件,并已登录 claude 账户
    • wsl 下已安装 claude code cli (2.0.73 或更高)
  2. 在 windows, C:\Users\\AppData\Local\Google\Chrome\User Data\NativeMessagingHosts (如果没有 NativeMessagingHosts 目录可自行创建 )下,新建

    • com.anthropic.claude_code_browser_extension.bat:
    @echo off
    wsl.exe -d Ubuntu -- /home/<WSL_USER>/.claude/chrome/chrome-native-host
    
    • com.anthropic.claude_code_browser_extension.json:
    { "name": "com.anthropic.claude_code_browser_extension", "description": "Claude Code Browser Extension Native Host", "path": "C:\\Users\\<USERNAME>\\AppData\\Local\\Google\\Chrome\\User Data\\NativeMessagingHosts\\com.anthropic.claude_code_browser_extension.bat", "type": "stdio", "allowed_origins": [ "chrome-extension://fcoeoabgfenejglbffodgkkbkcdhcgfn/" ] } 

    USERNAME、WSL_USER 分别替换成 windows、wsl 下的对应用户名

  3. 新建 win 注册表文件,install_registry.reg

    Windows Registry Editor Version 5.00
    
    [HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.anthropic.claude_code_browser_extension]
    @="C:\\Users\\<USERNAME>\\AppData\\Local\\Google\\Chrome\\User Data\\NativeMessagingHosts\\com.anthropic.claude_code_browser_extension.json" 

    USERNAME 替换成 windows 用户名,双击导入注册表

  4. 在 wsl 下执行:

    ln -s "/mnt/c/Users/<user>/AppData/Local/Google/Chrome/User Data/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn" ~/.config/google-chrome/Default/Extensions/fcoeoabgfenejglbffodgkkbkcdhcgfn
    

    将 user 替换成 Windows 的用户名。同时需要注意:
    (1) win 下的 claude chrome 插件是否安装在 Users//AppData/Local/Google/Chrome/User Data/Default/Extensions 目录。我最初卡在这儿 (没有找到 Default 目录),后来在本机搜索了下,发现插件安装在了 Users/AppData/Local/Google/Chrome/User Data/Profile1/Extensions 目录;
    (2) wsl 下~/.config/google-chrome 是否存在 Default/Extensions 目录,没有就新建一个。

  5. 登录 claude code 验证(claude --chrome),完结撒花 ~~~

参考链接:[FEATURE] Support Claude in Chrome for WSL environments · Issue #14367 · anthropics/claude-code · GitHub


📌 转载信息
转载时间:
2026/1/14 18:12:37

1. 添加模型提供商 (Model Providers)
要选择 Anthropic 协议!!!!!
要选择 Anthropic 协议!!!!!
要选择 Anthropic 协议!!!!!


2.Augment Proxy 设置,填写可用的 augment 账号信息,测试是否正常,填 url 测试后再填 token (我是这样)


3. 保存配置,启动插件,重新加载窗口。


大佬 @Ank 开发的插件链接
Release augment.vscode-augment@0.746.0 byok-internal · AnkRoot/Augment-BYOK


📌 转载信息
原作者:
lhllinuxdo
转载时间:
2026/1/14 18:06:53

Gemini Business 自动注册 & 2API 上传工具

基于大佬们的开源成果,整合了定时注册自动上传过期剔除等功能。实现全自动化的账号池维护。

核心逻辑优化

  • 自动维护:定时注册并直接传到 2API,自动剔除已过期账号,保留可用账号。
  • 失败重试:修改了注册机逻辑,设定申请 N 个,即使中间失败,也会一直重试直到成功申请到 N 个为止。
  • 丰俭由人:建议每 11 小时注册 2-3 个即可满足个人使用。避免对随机邮箱大佬提供不必要的压力。


感谢各位大佬的无私奉献:


环境准备

请确保已安装 Python 环境,并安装以下依赖库:

pip install undetected-chromedriver selenium beautifulsoup4 requests pystray pillow

配置说明

1. 先去 hf 部署一个 2api, 记住 API 地址admin_key
2. 新建并复制代码生成 py 文件,右键编辑,在顶部的配置区域填入你的 2API 信息:

 # 服务器 API 配置 API_HOST = "请输入你的服务器API地址" ADMIN_KEY = "请输入你的管理员密钥" # 无头模式开关 (True=后台运行无窗口, False=显示浏览器窗口) HEADLESS_MODE = True ##无头注册率会低点,但胜在静默,结合重试其实体验更好。 

运行脚本

在命令行中执行:

py gemini_auto.py

代码如下:

gemini_auto.py
"""
Gemini Business 自动注册上传工具
"""

# 标准库
import sys
import json
import time
import random
from pathlib import Path
from datetime import datetime, timedelta, timezone
from urllib.parse import urlparse, parse_qs
from concurrent.futures import ThreadPoolExecutor

# 第三方库
import requests
from bs4 import BeautifulSoup
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


# ==================== 配置区域 ====================
# 服务器 API 配置
API_HOST = "请输入你的服务器API地址"
ADMIN_KEY = "请输入你的管理员密钥"

# 临时邮箱 API 配置
MAIL_API = "https://mail.chatgpt.org.uk"
MAIL_KEY = "gpt-test"

# Gemini 登录页面
LOGIN_URL = "https://auth.business.gemini.google/login?continueUrl=https:%2F%2Fbusiness.gemini.google%2F&wiffid=CAoSJDIwNTlhYzBjLTVlMmMtNGUxZS1hY2JkLThmOGY2ZDE0ODM1Mg"

# 本地账号文件
ACCOUNTS_FILE = "accounts.json"

# 页面元素定位
XPATH = {
    "email_input": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[1]/div[1]/div/span[2]/input",
    "continue_btn": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[2]/div/button",
    "verify_btn": "/html/body/c-wiz/div/div/div[1]/div/div/div/form/div[2]/div/div[1]/span/div[1]/button",
}

# 随机姓名池
NAMES = [
    "James Smith", "John Johnson", "Robert Williams", "Michael Brown", "William Jones",
    "David Garcia", "Mary Miller", "Patricia Davis", "Jennifer Rodriguez", "Linda Martinez",
    "Elizabeth Taylor", "Richard Moore", "Susan Wilson", "Joseph Anderson", "Jessica Thomas",
    "Charles Jackson", "Sarah White", "Christopher Harris", "Karen Martin", "Daniel Thompson",
    "Thomas Garcia", "Nancy Martinez", "Matthew Robinson", "Lisa Clark", "Anthony Lewis",
    "Betty Walker", "Mark Young", "Margaret Allen", "Donald King", "Sandra Wright"
]

# 全局停止标志 (用于 GUI 停止任务)
STOP_FLAG = False

# 无头模式开关 (True=后台运行无窗口, False=显示浏览器窗口)
HEADLESS_MODE = True
# ==================================================


# ==================== 工具函数 ====================
def print_log(msg, level="INFO"):
    """统一日志输出格式"""
    icons = {"INFO": "→", "WARN": "⚠", "ERROR": "✗", "OK": "✓"}
    icon = icons.get(level, "•")
    print(f"{icon} {msg}")


def print_separator(char="=", length=80):
    """打印分隔线"""
    print(char * length)


def print_progress(current, total, success, fail, avg_time):
    """打印进度信息"""
    print(f"\n>>> 进度: {current}/{total} | 成功: {success} | 失败: {fail} | 平均耗时: {avg_time:.1f}s")


def log_error(email, error_msg):
    """记录错误到日志文件"""
    error_file = Path("errors.log")
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] 邮箱: {email} | 错误: {error_msg}\n"
    
    try:
        with open(error_file, "a", encoding="utf-8") as f:
            f.write(log_entry)
        print_log(f"错误已记录到 errors.log", "INFO")
    except Exception as e:
        print_log(f"写入错误日志失败: {e}", "WARN")


# ==================== 邮箱管理 ====================
email_queue = []


def create_temp_email():
    """创建临时邮箱地址"""
    try:
        response = requests.get(
            f"{MAIL_API}/api/generate-email",
            headers={"X-API-Key": MAIL_KEY},
            timeout=30
        )
        if response.status_code == 200 and response.json().get('success'):
            email = response.json()['data']['email']
            return email
    except Exception as e:
        print_log(f"邮箱服务异常: {e}", "❌")
    return None


def prefetch_email():
    """预创建邮箱并加入队列"""
    email = create_temp_email()
    if email:
        email_queue.append(email)


def get_email():
    """获取邮箱地址(优先使用队列中的)"""
    if email_queue:
        email = email_queue.pop(0)
        print_log(f"邮箱就绪 → {email}")
        return email
    
    email = create_temp_email()
    if email:
        print_log(f"已生成 → {email}")
    return email


def fetch_verification_code(email, timeout=60):
    """获取邮箱验证码"""
    print_log("等待邮件验证码...")
    start_time = time.time()
    
    while time.time() - start_time < timeout:
        try:
            response = requests.get(
                f"{MAIL_API}/api/emails",
                params={"email": email},
                headers={"X-API-Key": MAIL_KEY},
                timeout=10
            )
            
            if response.status_code == 200:
                emails = response.json().get('data', {}).get('emails', [])
                if emails:
                    html_content = emails[0].get('html_content') or emails[0].get('content', '')
                    soup = BeautifulSoup(html_content, 'html.parser')
                    code_element = soup.find('span', class_='verification-code')
                    
                    if code_element:
                        code = code_element.get_text().strip()
                        if len(code) == 6:
                            print_log(f"验证码 → {code}", "OK")
                            return code
        except:
            pass
        
        elapsed = int(time.time() - start_time)
        print(f"  等待中... ({elapsed}s)", end='\r')
        time.sleep(2)
    
    print_log("验证码超时,请检查网络", "ERROR")
    return None


# ==================== 账号注册 ====================
def save_account_config(email, driver, timeout=10):
    """提取并保存账号配置信息"""
    print_log(f"提取账号配置中(最多 {timeout}s)...")
    start_time = time.time()
    account_data = None

    while time.time() - start_time < timeout:
        cookies = driver.get_cookies()
        current_url = driver.current_url
        parsed_url = urlparse(current_url)

        # 提取 config_id
        url_parts = current_url.split('/')
        config_id = None
        for i, part in enumerate(url_parts):
            if part == 'cid' and i + 1 < len(url_parts):
                config_id = url_parts[i + 1].split('?')[0]
                break

        # 提取关键 cookies
        cookie_map = {c['name']: c for c in cookies}
        session_cookie = cookie_map.get('__Secure-C_SES', {})
        host_cookie = cookie_map.get('__Host-C_OSES', {})

        # 提取 csesidx
        csesidx = parse_qs(parsed_url.query).get('csesidx', [None])[0]

        # 验证所有必需字段
        if all([
            session_cookie.get('value'),
            host_cookie.get('value'),
            csesidx,
            config_id
        ]):
            expiry_timestamp = session_cookie.get('expiry', 0) - 43200
            expires_at = datetime.fromtimestamp(expiry_timestamp).strftime('%Y-%m-%d %H:%M:%S') if expiry_timestamp > 0 else None
            
            account_data = {
                "id": email,
                "csesidx": csesidx,
                "config_id": config_id,
                "secure_c_ses": session_cookie.get('value'),
                "host_c_oses": host_cookie.get('value'),
                "expires_at": expires_at
            }
            
            elapsed = time.time() - start_time
            print_log(f"配置提取完成 ({elapsed:.1f}s)", "OK")
            break

        time.sleep(1)

    if not account_data:
        print_log(f"配置不完整,已跳过 → {email}", "WARN")
        return None

    # 保存到文件
    existing_accounts = []
    if Path(ACCOUNTS_FILE).exists():
        try:
            with open(ACCOUNTS_FILE, 'r', encoding='utf-8') as f:
                existing_accounts = json.load(f)
        except:
            pass
    
    existing_accounts.append(account_data)
    
    with open(ACCOUNTS_FILE, 'w', encoding='utf-8') as f:
        json.dump(existing_accounts, f, indent=2, ensure_ascii=False)
    
    print_log(f"已保存 → {ACCOUNTS_FILE}", "OK")
    return account_data


def fast_type(element, text, delay=0.02):
    """快速输入文本"""
    for c in text:
        element.send_keys(c)
        time.sleep(delay)


def register_single_account(driver, executor):
    """注册单个账号 (来自 app.py 的简洁版本)"""
    start_time = time.time()
    email = get_email()
    if not email:
        return None, False, None, 0

    wait = WebDriverWait(driver, 30)

    try:
        # 1. 访问登录页
        driver.get(LOGIN_URL)
        
        # 检测空白页
        time.sleep(2)
        page_source = driver.page_source
        if len(page_source) < 500 or "about:blank" in driver.current_url:
            raise Exception("页面加载空白,需要重启浏览器")

        # 2. 输入邮箱
        print_log("输入邮箱...")
        inp = wait.until(EC.element_to_be_clickable((By.XPATH, XPATH["email_input"])))
        inp.click()
        inp.clear()
        fast_type(inp, email)
        
        # 验证邮箱是否成功输入
        time.sleep(0.3)
        actual_value = inp.get_attribute("value")
        if actual_value != email:
            print_log(f"输入验证失败,清空后重新输入...", "WARN")
            # 清空后用 JS 输入
            driver.execute_script("arguments[0].value = '';", inp)
            time.sleep(0.1)
            driver.execute_script("arguments[0].value = arguments[1];", inp, email)
            # 触发 input 事件
            driver.execute_script("""
                var event = new Event('input', { bubbles: true });
                arguments[0].dispatchEvent(event);
            """, inp)
            time.sleep(0.3)
        
        print_log(f"邮箱 → {email}", "OK")

        # 3. 点击继续
        time.sleep(0.5)
        btn = wait.until(EC.element_to_be_clickable((By.XPATH, XPATH["continue_btn"])))
        driver.execute_script("arguments[0].click();", btn)
        print_log("继续下一步", "OK")

        # 异步预创建下一个邮箱
        executor.submit(prefetch_email)

        # 4. 获取验证码
        time.sleep(2)
        code = fetch_verification_code(email)
        if not code:
            return email, False, None, time.time() - start_time

        # 5. 输入验证码
        time.sleep(1)
        print_log(f"输入验证码 → {code}")
        try:
            pin = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "input[name='pinInput']")))
            pin.click()
            time.sleep(0.1)
            fast_type(pin, code, 0.05)
        except:
            try:
                span = driver.find_element(By.CSS_SELECTOR, "span[data-index='0']")
                span.click()
                time.sleep(0.2)
                driver.switch_to.active_element.send_keys(code)
            except Exception as e:
                print_log(f"验证码输入失败: {e}", "ERROR")
                return email, False, None, time.time() - start_time

        # 6. 点击验证
        time.sleep(0.5)
        try:
            vbtn = driver.find_element(By.XPATH, XPATH["verify_btn"])
            driver.execute_script("arguments[0].click();", vbtn)
        except:
            for btn in driver.find_elements(By.TAG_NAME, "button"):
                if '验证' in btn.text:
                    driver.execute_script("arguments[0].click();", btn)
                    break
        print_log("提交验证", "OK")

        # 7. 输入姓名
        print_log("等待姓名输入...")
        selectors = [
            "input[formcontrolname='fullName']",
            "input[placeholder='全名']",
            "input[placeholder='Full name']",
            "input#mat-input-0",
        ]
        name_inp = None

        # 轮询检测姓名输入框
        for _ in range(30):
            for sel in selectors:
                try:
                    name_inp = driver.find_element(By.CSS_SELECTOR, sel)
                    if name_inp.is_displayed():
                        break
                except:
                    continue
            if name_inp and name_inp.is_displayed():
                break
            time.sleep(1)

        if name_inp and name_inp.is_displayed():
            name = random.choice(NAMES)
            name_inp.click()
            time.sleep(0.2)
            name_inp.clear()
            fast_type(name_inp, name)
            print_log(f"姓名 → {name}", "OK")
            time.sleep(0.3)
            name_inp.send_keys(Keys.ENTER)
            time.sleep(1)
        else:
            print_log("未找到姓名输入框", "ERROR")
            return email, False, None, time.time() - start_time

        # 8. 等待进入工作台
        print_log("等待工作台...")
        for _ in range(30):
            time.sleep(1)
            url = driver.current_url
            if 'business.gemini.google' in url and '/cid/' in url:
                print_log("工作台加载完成", "OK")
                break
        else:
            print_log(f"未跳转到工作台 → {driver.current_url}", "WARN")

        # 9. 保存配置
        elapsed = time.time() - start_time
        config = save_account_config(email, driver)
        if config:
            print_log(f"注册成功 → {email} (耗时 {elapsed:.1f}s)", "OK")
            return email, True, config, elapsed
        return email, False, None, elapsed

    except Exception as e:
        print_log(f"注册异常: {e}", "ERROR")
        log_error(email, str(e))
        return email, False, None, time.time() - start_time


# ==================== 账号上传 ====================
class AccountUploader:
    """账号上传管理类"""
    
    def __init__(self, api_host, admin_key):
        self.api_host = api_host.rstrip('/')
        self.admin_key = admin_key
        self.session = requests.Session()
        
    def login(self):
        """登录到服务器"""
        print_log("连接服务器中...")
        login_url = f"{self.api_host}/login"
        
        try:
            response = self.session.post(
                login_url,
                data={"admin_key": self.admin_key},
                allow_redirects=True,
                timeout=30
            )
            
            if len(self.session.cookies) > 0:
                print_log("服务器连接成功", "OK")
                return True
            
            if response.status_code == 200 and '登录' in response.text:
                print_log("密钥验证失败", "ERROR")
                return False
            
            print_log("服务器连接失败", "ERROR")
            return False
                
        except Exception as e:
            print_log(f"连接异常: {e}", "ERROR")
            return False
    
    def upload_and_replace(self, file_path):
        """覆盖上传账号配置"""
        if not Path(file_path).exists():
            print_log(f"文件不存在 → {file_path}", "ERROR")
            return False
        
        print_log(f"读取本地文件 → {file_path}")
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                accounts_data = json.load(f)
        except Exception as e:
            print_log(f"文件读取异常: {e}", "ERROR")
            return False
        
        print_log(f"本地账号 → {len(accounts_data)} 个")
        print_log("开始上传...")
        
        upload_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.put(
                upload_url,
                json=accounts_data,
                timeout=30
            )
            
            if response.status_code == 200:
                result = response.json()
                print_log("上传完成!", "OK")
                print_log(f"{result.get('message', '配置已更新')}")
                print_log(f"服务器账号 → {result.get('account_count', len(accounts_data))} 个")
                
                print()
                print_separator()
                print_log("正在获取服务器账号状态...")
                print_separator()
                self.view_accounts()
                
                return True
            else:
                print_log(f"上传失败,状态码: {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"上传异常: {e}", "ERROR")
            return False
    
    def upload_and_merge(self, file_path):
        """合并上传账号配置(保留远程正常账号)"""
        print_log("智能合并模式启动...")
        
        # 读取本地账号
        if not Path(file_path).exists():
            print_log(f"本地文件缺失 → {file_path}", "ERROR")
            return False
        
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                local_accounts = json.load(f)
            print_log(f"本地账号 → {len(local_accounts)} 个")
        except Exception as e:
            print_log(f"读取本地文件失败: {e}", "ERROR")
            return False
        
        # 获取远程账号配置
        print_log("获取远程配置...")
        config_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.get(config_url, timeout=30)
            if response.status_code == 200:
                remote_config = response.json()
                remote_accounts = remote_config.get('accounts', [])
                print_log(f"远程账号 → {len(remote_accounts)} 个")
            else:
                print_log("远程配置获取失败,仅上传本地", "WARN")
                remote_accounts = []
        except Exception as e:
            print_log(f"远程连接异常: {e},仅上传本地", "WARN")
            remote_accounts = []
        
        # 筛选远程正常账号(未过期、未禁用)
        valid_remote_accounts = []
        for account in remote_accounts:
            if account.get('disabled', False):
                continue
            
            expires_at = account.get('expires_at')
            if expires_at and expires_at != '未设置':
                try:
                    beijing_tz = timezone(timedelta(hours=8))
                    expire_time = datetime.strptime(expires_at, "%Y-%m-%d %H:%M:%S")
                    expire_time = expire_time.replace(tzinfo=beijing_tz)
                    current_time = datetime.now(beijing_tz)
                    if expire_time <= current_time:
                        continue
                except:
                    pass
            
            valid_remote_accounts.append(account)
        
        print_log(f"有效远程账号 → {len(valid_remote_accounts)} 个")
        
        # 合并账号(去重)
        merged_accounts = list(valid_remote_accounts)
        remote_ids = {acc.get('id') for acc in valid_remote_accounts}
        
        new_count = 0
        for local_account in local_accounts:
            local_id = local_account.get('id')
            if local_id not in remote_ids:
                merged_accounts.append(local_account)
                new_count += 1
        
        print_log(f"合并结果 → 保留 {len(valid_remote_accounts)} 个,新增 {new_count} 个,共 {len(merged_accounts)} 个")
        
        # 上传合并后的配置
        print_log("上传合并配置...")
        upload_url = f"{self.api_host}/accounts-config"
        
        try:
            response = self.session.put(
                upload_url,
                json=merged_accounts,
                timeout=30
            )
            
            if response.status_code == 200:
                result = response.json()
                print_log("合并上传完成!", "OK")
                print_log(f"{result.get('message', '配置已更新')}")
                print_log(f"服务器账号 → {result.get('account_count', len(merged_accounts))} 个")
                
                print()
                print_separator()
                print_log("正在获取服务器账号状态...")
                print_separator()
                self.view_accounts()
                
                return True
            else:
                print_log(f"上传失败,状态码: {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"上传异常: {e}", "ERROR")
            return False
    
    def view_accounts(self):
        """查看远程账号状态"""
        print_log("查询远程账号...")
        
        view_url = f"{self.api_host}/accounts"
        
        try:
            response = self.session.get(view_url, timeout=30)
            
            if response.status_code == 200:
                data = response.json()
                accounts = data.get('accounts', [])
                total = data.get('total', len(accounts))
                
                if not accounts:
                    print_log("远程无账号配置", "INFO")
                    return True
                
                print(f"\n共 {total} 个账号")
                print_separator("=", 120)
                
                # 表头
                print(f"{'序号':<6} {'账号ID':<35} {'状态':<12} {'过期时间':<22} {'剩余时长':<15} {'累计对话':<10}")
                print_separator("-", 120)
                
                # 账号列表
                for i, account in enumerate(accounts, 1):
                    acc_id = account.get('id', 'N/A')
                    status = account.get('status', 'N/A')
                    expires_at = account.get('expires_at', '未设置')
                    remaining = account.get('remaining_display', 'N/A')
                    conversations = account.get('conversation_count', 0)
                    
                    if len(acc_id) > 33:
                        acc_id = acc_id[:30] + "..."
                    
                    print(f"{i:<6} {acc_id:<35} {status:<12} {expires_at:<22} {remaining:<15} {conversations:<10}")
                
                print_separator("=", 120)
                return True
            else:
                print_log(f"查询失败 → 状态码 {response.status_code}", "ERROR")
                return False
                
        except Exception as e:
            print_log(f"查询异常: {e}", "ERROR")
            return False


# ==================== 主程序流程 ====================
def run_batch_registration(target_count):
    """批量注册账号 (保底成功数模式)"""
    print()
    print_separator()
    print(f"目标: 成功注册 {target_count} 个账号")
    print_separator()
    print()
    
    # 清空旧文件
    if Path(ACCOUNTS_FILE).exists():
        Path(ACCOUNTS_FILE).unlink()
        print_log(f"已清空 → {ACCOUNTS_FILE}")
    
    driver = None
    executor = ThreadPoolExecutor(max_workers=2)
    success_count = 0
    fail_count = 0
    attempt_count = 0
    total_time = 0
    success_times = []

    # 预创建第一个邮箱
    executor.submit(prefetch_email)
    
    # 连续失败计数器(用于保护机制)
    consecutive_fails = 0
    MAX_CONSECUTIVE_FAILS = 20

    # 循环直到成功数达到目标
    while success_count < target_count:
        # 检查全局停止标志
        global STOP_FLAG
        if STOP_FLAG:
            print_log("收到停止信号,中止任务", "WARN")
            STOP_FLAG = False  # 重置标志
            break
        
        # 连续失败保护
        if consecutive_fails >= MAX_CONSECUTIVE_FAILS:
            print_log(f"连续失败 {MAX_CONSECUTIVE_FAILS} 次,中止本轮任务", "ERROR")
            break
        
        attempt_count += 1
        current_target = target_count + fail_count  # 动态调整显示的总数
        
        print()
        print_separator("#", 60)
        print(f"正在注册第 {attempt_count} 个账号 (成功: {success_count}/{target_count})")
        print_separator("#", 60)
        print()

        # 确保浏览器可用
        if driver is None:
            options = uc.ChromeOptions()
            if HEADLESS_MODE:
                print_log("启动无头浏览器...")
                options.add_argument("--headless=new")
                options.add_argument("--disable-gpu")
                options.add_argument("--no-sandbox")
                options.add_argument("--window-size=1200,800")
            else:
                print_log("启动浏览器...")
            driver = uc.Chrome(options=options, use_subprocess=True)
            if not HEADLESS_MODE:
                driver.set_window_size(100, 200)
                driver.set_window_position(50, 50)
            time.sleep(1)
        else:
            try:
                _ = driver.current_url
            except:
                print_log("浏览器已关闭,重启中...")
                try: 
                    driver.quit()
                except: 
                    pass
                options = uc.ChromeOptions()
                if HEADLESS_MODE:
                    options.add_argument("--headless=new")
                    options.add_argument("--disable-gpu")
                    options.add_argument("--no-sandbox")
                    options.add_argument("--window-size=1200,800")
                driver = uc.Chrome(options=options, use_subprocess=True)
                if not HEADLESS_MODE:
                    driver.set_window_size(100, 200)
                    driver.set_window_position(50, 50)
                time.sleep(1)

        try:
            email, success, config, elapsed = register_single_account(driver, executor)
            total_time += elapsed
            
            if success and config:
                success_count += 1
                success_times.append(elapsed)
                consecutive_fails = 0  # 重置连续失败计数
                print_log(f"进度: {success_count}/{target_count} 完成", "OK")
            else:
                fail_count += 1
                consecutive_fails += 1
                print_log(f"失败 +1 (连续失败: {consecutive_fails}/{MAX_CONSECUTIVE_FAILS})", "WARN")
                
        except Exception as e:
            error_msg = str(e).lower()
            print_log(f"注册异常: {e}", "ERROR")
            fail_count += 1
            consecutive_fails += 1
            
            # 检测空白页或页面加载问题
            if "blank" in error_msg or "timeout" in error_msg or "element" in error_msg:
                print_log("检测到页面异常,重启浏览器...", "WARN")
                if driver:
                    try: 
                        driver.quit()
                    except: 
                        pass
                    driver = None
            elif driver:
                try: 
                    driver.quit()
                except: 
                    pass
                driver = None

        avg_time = total_time / attempt_count if total_time > 0 else 0
        print_progress(success_count, target_count, success_count, fail_count, avg_time)

        if success_count < target_count and driver:
            try:
                driver.delete_all_cookies()
            except:
                pass
            time.sleep(random.randint(2, 3))

    executor.shutdown(wait=False)
    if driver:
        try: 
            driver.quit()
        except: 
            pass
        
        # Monkeypatch: 防止 __del__ 再次调用 quit 导致 WinError 6
        try:
            driver.quit = lambda: None
        except:
            pass
            
        driver = None

    # 统计信息
    avg_time = sum(success_times) / len(success_times) if success_times else 0
    min_time = min(success_times) if success_times else 0
    max_time = max(success_times) if success_times else 0
    
    print()
    print_separator()
    print(f"注册完成! 目标: {target_count}, 成功: {success_count}, 失败: {fail_count}, 总尝试: {attempt_count}")
    print(f"总耗时: {total_time:.1f}s | 平均: {avg_time:.1f}s | 最快: {min_time:.1f}s | 最慢: {max_time:.1f}s")
    print(f"账号已保存至: {ACCOUNTS_FILE}")
    print_separator()
    
    return {
        "success": success_count,
        "fail": fail_count,
        "attempts": attempt_count,
        "avg_time": avg_time,
        "success_times": success_times,
        "is_ok": success_count > 0
    }


def handle_task_execution(count, upload_mode, uploader):
    """执行一次完整的任务(注册+上传)"""
    stats = run_batch_registration(count)
    
    if stats.get('is_ok'):
        print()
        # 先登录再上传
        if not uploader.login():
            print_log("服务器登录失败,无法上传", "ERROR")
            return stats
        
        if upload_mode == 'replace':
            print_log("开始覆盖上传到服务器...")
            uploader.upload_and_replace(ACCOUNTS_FILE)
        elif upload_mode == 'merge':
            print_log("开始合并上传到服务器...")
            uploader.upload_and_merge(ACCOUNTS_FILE)
    else:
        print_log("注册流程未成功,取消上传", "WARN")
        
    return stats


def main():
    """主程序入口"""
    print_separator()
    print("Gemini Business 自动注册上传工具")
    print_separator()
    print()
    
    uploader = AccountUploader(API_HOST, ADMIN_KEY)
    
    # 登录服务器
    if not uploader.login():
        print_log("登录失败,无法继续", "ERROR")
        input("\n按回车键退出...")
        sys.exit(1)
    
    print()
    
    while True:
        print("\n请选择操作:")
        print("  1. 注册上传")
        print("  2. 查看远程账号状态")
        print("  3. 退出")
        print()
        
        choice = input("请输入选项 (1-3): ").strip()
        
        if choice == "1":
            # 确定上传模式
            upload_mode = 'merge'
            
            # 1. 询问数量
            count_str = input("\n请输入注册数量 (默认 5): ").strip()
            count = int(count_str) if count_str else 5
            
            # 2. 询问执行模式
            print("\n请选择执行模式:")
            print("  1. 立即执行一次")
            print("  2. 定时循环执行 (支持自定义间隔)")
            mode_choice = input("请输入选项 (1-2): ").strip()
            
            if mode_choice == "2":
                # 定时模式
                hours_str = input("\n请输入循环间隔小时 (默认 12): ").strip()
                try:
                    interval_hours = float(hours_str) if hours_str else 12.0
                except:
                    print_log("输入无效,使用默认值 12 小时", "WARN")
                    interval_hours = 12.0
                
                print(f"\n已选择: 定时循环模式 (间隔 {interval_hours} 小时)")
                run_now_str = input("是否在开始循环前立即运行一次? (y/n, 默认 y): ").strip().lower()
                run_now = run_now_str != 'n'
                
                print_log("定时任务已启动! 按 Ctrl+C 可随时停止", "INFO")
                
                loop_count = 0
                while True:
                    loop_count += 1
                    
                    if run_now or loop_count > 1:
                        print(f"\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] >>> 开始第 {loop_count} 次循环任务")
                        handle_task_execution(count, upload_mode, uploader)
                        print_log(f"第 {loop_count} 次任务完成", "INFO")
                    else:
                        print_log("跳过首次运行,直接进入等待", "INFO")

                    # 计算下次运行时间
                    next_run = datetime.now() + timedelta(hours=interval_hours)
                    print_log(f"下一次任务将在 {next_run.strftime('%Y-%m-%d %H:%M:%S')} 开始", "INFO")
                    
                    # 倒计时等待
                    total_seconds = int(interval_hours * 3600)
                    try:
                        while total_seconds > 0:
                            # 每分钟更新一次状态,显示剩余时间
                            if total_seconds % 60 == 0:
                                pass 
                            time.sleep(1)
                            total_seconds -= 1
                    except KeyboardInterrupt:
                        print("\n")
                        print_log("检测到中断, 停止定时任务", "WARN")
                        break
                        
            else:
                # 立即执行模式 (默认)
                print()
                handle_task_execution(count, upload_mode, uploader)
                
        elif choice == "2":
            print()
            uploader.view_accounts()
            
        elif choice == "3":
            print("\n再见!")
            break
            
        else:
            print_log("无效选项,请重试", "WARN")
        
        print()


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n")
        print_log("用户中断程序", "INFO")
    except Exception as e:
        print_log(f"程序异常: {e}", "ERROR")
        input("\n按回车键退出...")


📌 转载信息
原作者:
zding
转载时间:
2026/1/14 18:06:03

一个简单易用的局域网唤醒工具,支持通过 Web 界面管理主机、发送 WOL 唤醒包、检测服务器状态以及测试端口连通性。

功能特性

  • 主机管理(添加、编辑、删除)
  • WOL 唤醒发送 Magic Packet
  • 服务器状态检测(ping/SSH/RDP 并发检测)
  • 端口连通性测试(3 秒超时)
  • 数据导入导出(JSON 格式)
  • 密码保护功能
  • 深色 / 浅色主题切换,兼容移动端

项目地址

https://github.com/dhjz/dwol

功能示例图



📌 转载信息
原作者:
ddjhh
转载时间:
2026/1/14 18:03:56

https://linux.do/t/topic/1436106?u=yeahhe

英伟达的 api 无法用浏览器网页纯前端调用,所以我用 GLM 搭了一个中转 URL,解决 CORS 问题

https://p1609eqjhck0-d.space.z.ai/api/nim/v1/chat/completions

效果:



📌 转载信息
原作者:
yeahhe
转载时间:
2026/1/14 18:00:10

引言:加入 L 站以来,小白也算是慢慢折腾,了解机场 VPS 等一系列网络基础设施概念后,尝试自己当一回赛博工人,借鉴大伙的智慧,手把手从里到外开始建站,装点一下属于自己的博客。再就是自己在 L 站也水了太多,没有自己的产出,督促自己写一篇文档,记录自己折腾的日子。整篇文档仅供参考,望大家轻喷。

参考资料:
1.[教程] Cloudflare 单域名 SaaS 优选教程。让 Cloudflare 不再成为中国减速器,最低延迟低至 10ms!
2. 菜鸟教程零基础 Cloudflare 优选教程
3.0 成本 10 分钟用 Cloudflare + GitHub + Astro 搭建一个全球秒开、永久免费的顶级个人博客

技术栈简要

  • 博客托管:Netlify(稳定、免费、支持 Astro SSR)
  • 优选传输:Cloudflare SaaS(免费证书、自定义主机名)
  • 智能分流:DNSPod(国内优选,海外直连)

当然整个搭建过程也不是一帆风顺的,在我看来并不是按照大佬们的操作指示,一步一步来就会成功,这其中涉及到许多概念的理解,感谢新时代的 AI 技术为我答疑解惑。接着来分别阐述一下为什么选这些技术栈,具体技术就不做介绍了,这里着重说明为什么选 A 而不选 B 此类的问题。

整体流程

[ 用户浏览器]
      |
| 1. DNS查询: "主站在哪?"
v
[ DNSPod 智能解析 ] | |
| (国内用户) | (境外用户)
| 2a. 返回【优选IP】 | 2b. 返回【官方Anycast IP】
| (来自 saas.sin.fan) | (来自 cloudflare 回源域名)
v v
[ Cloudflare 边缘节点 ] <================================+ |
| 3. 携带请求头: Host: 主站
| 识别 SaaS 证书, 建立加密连接 (TLS)
|
| 4. 触发 Fallback Origin (回源后备)
| 通过内部骨干网转发至: 回源域名
v
[ Netlify 目标服务器 ] |
| 5. 匹配 Domain Alias (域名别名)
| 确认 Host: 主站域名是“自己人”
|
| 6. 读取 Astro 构建的静态文件 (dist)
| 返回数据 (且不触发 301 重定向)
v
[ 用户浏览器 (成功渲染) ]

用简单的词汇描述一下整个过程,总的来说需要四个域名,其中两个是我们自己准备并托管的(A 和 B),另外两个分别是第三方服务分配或者维护的(C 和 D),我们只需要拿来用即可:

  • 主域名 A(店面招牌):相当于你经营了一家店,大家记住了你的店名,全世界的人都根据这个店名来访问,只提供了一条引路的作用,在资源访问等涉及效率方面没有优化
  • 回源域名 B(内部通道):托管在 Cloudflare 上(后文简称 CF ),因为 CF 在世界范围内有大量机房(距报告说的是每天 20% 的流量都会从它家走,还是很恐怖的),加上 Anycast 技术可以让你接入延迟最低的 CF 网络。由于主域名 A 不在 CF 托管,CF 默认不让进门,于是需要一个 “内部员工” B 来为 A 担保,相当于访问 A 也可以走 CF 专线了
  • 优选域名 C(专业导航):由三方大佬维护,既不存博客内容,也不管店名,作用是每天都去看哪条路接入 CF 是最快的,然后把这些最快的路告诉你们,实现绕过公网堵塞,实现秒开
  • pages 域名 D(内容仓库):在各种平台站托管时会给你分配一个 pages 域名,这是真正存放博客网站源文件的地方,只负责把网页内容通过回源域名 B 输出去

总结:数据怎么跑的?

  1. 用户看你的招牌(A)
  2. 招牌(A)根据导航(C)的建议,把用户引向最快的 CF 大门
  3. 大门(CF)看到是自己人(B)担保的请求,立即放行
  4. 大门内部通过专线去仓库(D)获取网页内容
  5. 原路返回用户

博客托管

这里解决的是博客存储在哪的问题,刚开始时我以为博客文件都要存在 VPS 服务器上,然后请求的数据流是 [主机 → VPS],后来我才发现静态博客大多采取第三方托管的方式,这里比较有名的托管服务有 Cloudflare Pages, Netlify, Vercel, GitHub Pages 等等。

首先尝试了 Cloudflare Pages,但 Cloudflare Pages 的问题在于,不托管在 Cloudflare 上的域名无法添加到 Pages 主页的自定义域名,具体情况如下图所示。

Cloudflare Pages 行不通,就去看看别人家。由于我搭建博客的时候阅读了 Astro 的官方文档,其中建议我在 Netlify 进行托管,后续的操作中我也就这么做了,整体没有遇到 Cloudflare 那样不认域名的问题,在此不赘述有关 Netlify 具体如何托管博客站,文档非常明白。

图:Astro 官方推荐的托管方式

图:在 Netlify 托管可以手动添加三方域名

优选传输

为什么采取两个域名分开托管的方式?

这是由 CF 自己的托管逻辑决定的,一旦你开启了小黄云托管,CF 会自动接管域名流量,分配一组 Anycast IP,但在 CF 内部,用户是无法修改这一部分的 IP 的,也就是说无法根据外部线路实现动态优化,在大陆内的访问无法得到保障。

在这样的情况下,引入 CF 的 SaaS (Custom Hostnames)服务,即使我们的主域名不在 CF 托管,只要有一个回源域名在担保,也可以给主域名访问提供加速和 SSL 证书。

主域名在 CF 外,我们可以自己定义 DNS 怎么解析,分线路优化。回源域名在 CF 内,保留了海外访问加速通道。

具体到我的情况,我的主域名是在阿里云购买的,然后在 DNSHE 免费申请了一个.de5.net 的域名当回源,藏在后面的域名,不用太好看,能用就行。

图:DNSHE 免费域名

下列为托管的具体步骤,参考了 MIYUSAMA 的贴菜鸟教程零基础 Cloudflare 优选教程,自认为非常详细了,几乎一模一样,有不懂的可以问问 Gemini 等 AI,然后实在不知道的,我有空看到了也会回复大家。

智能分流

一开始我的主域名托管在阿里云上的,毕竟就是从阿里买的,平台的技术力也毋庸置疑,但是用着用着发现,阿里云 DNS 解析没办法指定海外线路,这一点在分流的时候影响还挺大的。相比而言,同样免费套餐的腾讯 DNSPod 默认有海外线路解析。

图:阿里云免费版不支持选取境外解析线路

图:DNSPod 解析列表有境外解析选项

DNS 托管迁移很简单,以我从阿里云托管到腾讯为例,首先找到腾讯的 NX 服务器,一般来说是一对,然后填入阿里云的 NX 服务器配置就行了,这一步的作用是,腾讯告诉阿里,我拿到了这个域名的解析权,后面你就不用管了。

优选结果

图:优选前,DNS 解析权托管在 CF 上

优选后,DNS 解析托管在 DNSPod 上,加速线路分别交给 CF 和 saas.sin.fan (看起来好像没什么变化)

失败的尝试

cloudflare 优选域名加速个人博客求助

L 站很多帖子是对子域名进行优化,技术路线是:CF 回源域名 + CF Pages + 主域名阿里云解析,这一套方法在我尝试下来行不通,原因和前文提到的一样,无法将托管在其他平台上的主域名,加入到 Pages 的自定义域名列表中。

Gemini 给出过解决方案,重写 rules 或者 用 workers,主要思路是改 Host 头为主域名,但实际没有成功过,只能访问回源域名,然后主域名解析不到内容,我猜测原因是在同一个 CF 账号下,Pages 的自动路由策略会干扰 SaaS 的手动回源策略,导致回源域名根本拉不动 Pages 的内容。

图:Gemini 的胡说八道

结束语

技术在于折腾,看起来好像没啥提速效果,但是从整个网络数据流走向,以及架构方面,我有了很多新的收获,也感谢 L 站的各位积极分享!


📌 转载信息
转载时间:
2026/1/14 17:59:59

错误示例:

error":{“message”:“Post "https://open.bigmodel.cn/api/paas/v4/chat/completions\”: context canceled",“type”:“server_error”,“code”:“internal_server_error”}
错误代码:5XX

解决方法

  1. 调整 Claude Code 默认配置
    Cluade Code 新版本增加了许多 API 的超时检测,请确保在环境变量中完整添加以下所有字段:
 "API_TIMEOUT_MS": "600000", "BASH_DEFAULT_TIMEOUT_MS": "600000", "BASH_MAX_TIMEOUT_MS": "600000", "CLAUDE_API_TIMEOUT": "600000", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1", "CLAUDE_CODE_MAX_OUTPUT_TOKENS": "32000", "MCP_TIMEOUT": "300000", "MCP_TOOL_TIMEOUT": "600000" 
  1. 确保中间层(如 Cloudflare、Edgeone 等加速和防护服务)设置正确
    近期在 Claude Code 等客户端里出现的 5XX 报错(例如返回体含 “context canceled”“server_error”“internal_server_error”)在不少情况下并非模型服务本身异常,而是链路中间层的回源超时导致连接被提前断开。我们确认当请求经过 Cloudflare 或 EdgeOne 这类 CDN / 代理后,如果上游在长思考、长文本推理、工具调用或搜索阶段出现较长时间未产生任何响应字节,CDN / 代理可能会按 “源站无响应” 判定超时并主动断链;客户端侧就会表现为请求被取消、连接中止,进而映射成 5XX 或类似的 server_error。该问题在非流式响应更常见,尤其是 Claude Code 执行后台任务或子代理调用时往往走非流式路径,容易在几十秒到一分钟的空窗内触发超时。

处理建议:优先在 Cloudflare 或 EdgeOne 的规则引擎中,将相关接口的 “源站超时 / 回源超时 / HTTP 应答超时” 提高到能够覆盖最坏情况下的推理时长,建议至少 180 秒,必要时更高;并尽量只对特定域名或特定 Path 生效,避免影响全站。若所用方案或回源协议不支持将超时调到目标值,建议改为流式输出或加入周期性心跳字节(保持连接持续有数据流动),以避免被中间层误判为无响应。(CPA 新版本在流式输出时,提供此类问题的解决方案,例如持续发送空行作为流式返回内容)

  1. Nginx 配置 (若有)
    参考这位佬友的回复

解决在 claude code 等工具中接入 API 使用(尤其是 CLIProxyAPI 等服务)频发 500 报错的若干方法 - #3,来自 geq1fan


📌 转载信息
原作者:
moxiyan
转载时间:
2026/1/14 17:47:26

故事背景

用 SolidVPS 75 刀大鸡(没有 GPU),建了个 open-webui。知识库文件 3000 + 份。本地跑 bge-m3 时 cpu 负载 50%,且速度慢。因此转入 外部 API 方案。期间顺便换到 qwen-embeding-8B 试了试。

遇到速率限制问题

无论 Nvidia 还是硅基流动,都有 TPM RPM 限制。而我没找到 open-webui 里对外部嵌入模型发起请求的速率限制,因此知识库重建索引时,Nvidia 和 硅基 都容易因为 TPM 返回失败,进而导致 open-webui 无法获取到正确的向量。

解决方案

建站了,自然自带 nginx(我用 nginx-ui)进行管理。加一个流控


    # ======================================================
    # 区域 1: Nvidia (Embedding)
    # 限制: 40 RPM (每分钟40次)
    # 策略: 严格排队
    # ======================================================
    # 使用 "global" 作为 key,表示全局限制,不是按 IP
    limit_req_zone "global" zone=nvidia_limit:10m rate=40r/m;

    # ======================================================
    # 区域 2: SiliconFlow (Reranker)
    # 限制: 2000 RPM (每分钟2000次)
    # 策略: 允许突发
    # ======================================================
    limit_req_zone "global" zone=silicon_limit:10m rate=2000r/m;
    
       # ------------------------------------------------------
    # 服务 1: 代理 Nvidia (监听 8090)
    # ------------------------------------------------------
    server {
        listen 8090;
        
        location / {
            # 允许突发 100 个请求排队
            limit_req zone=nvidia_limit burst=100;
            
            proxy_pass https://integrate.api.nvidia.com;
            proxy_ssl_server_name on;
            proxy_set_header Host integrate.api.nvidia.com;
            
            # 增加超时时间,防止排队太久断开
            proxy_read_timeout 600s;
        }
    }

    # ------------------------------------------------------
    # 服务 2: 代理 SiliconFlow (监听 8091)
    # ------------------------------------------------------
    server {
        listen 8091;

        location / {
            # 允许突发 500 个请求排队
            # 2000r/m 很快,加上 nodelay 可以让请求瞬间转发,
            # 只有超过突发阈值时才开始排队或拒绝。
            # 这里不加 nodelay,保持平滑流控效果。
            limit_req zone=silicon_limit burst=500;

            proxy_pass https://api.siliconflow.cn;
            proxy_ssl_server_name on;
            proxy_set_header Host api.siliconflow.cn;
            
            
             # 增加超时时间,防止排队太久断开
            proxy_read_timeout 600s;
        }
    }

# Server 2: 回源专用 (origin-chat.example.com)
# 强制校验 Header,校验失败直接 444
server {
    listen 2083 ssl;
    listen [::]:2083 ssl;
    http2 on;
    server_name origin-chat.example.com;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers off;
    ssl_certificate /etc/nginx/ssl/cf_origin_server_2048/fullchain.cer;
    ssl_certificate_key /etc/nginx/ssl/cf_origin_server_2048/private.key;
    # --------------------------------------------------------
    # [核心] 请求头校验逻辑,仅限认证的前置CDN回源
    # --------------------------------------------------------
    # 假设 Header 为 X-Origin-Verify,值为 strict-token-123456
    # 如果不相等 (!=),则直接返回 403
    # if ($http_x_origin_verify != "strict-token-123456" ) {
    # return 444;
    # }
    # --------------------------------------------------------
    # 业务逻辑 (与上方保持一致,确保后端处理逻辑相同)
    location /.well-known/acme-challenge {
        proxy_set_header Host $host;
        proxy_set_header X-Real_IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
        proxy_pass http://127.0.0.1:9180;
    }
    location ~* ^/(auth|api|oauth|admin|signin|signup|signout|login|logout|sso)/ {
        proxy_pass http://openwebui:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # 注意:这里依然传递 chat 域名给后端,保证应用识别正确的主站域名
        proxy_set_header Host chat.quarkmed.com;
        proxy_read_timeout 10m;
        proxy_buffering off;
        client_max_body_size 20M;
        proxy_no_cache 1;
        proxy_cache_bypass 1;
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
        add_header Pragma "no-cache" always;
        expires -1;
    }
    location ~* \.(css|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://openwebui:8080;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host chat.quarkmed.com;
        expires 7d;
        add_header Cache-Control "public, immutable";
    }
    location / {
        proxy_pass http://openwebui:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host chat.quarkmed.com;
        proxy_read_timeout 10m;
        proxy_buffering off;
        client_max_body_size 20M;
        add_header Cache-Control "public, max-age=300, must-revalidate";
    }
}

open-webui api 地址改为 nginx: 端口


📌 转载信息
原作者:
lekai
转载时间:
2026/1/14 17:47:23

之前在站里刷帖子看见一位佬友推荐了一个 github 项目。可以无需开启 TUN 模式让反重力稳定走代理。当时有其他的事,一直没体验。今天尝试了一下,的确能行。在 cursor 里也能使用。【项目地址】


使用方式
我用的是 windows,直接点击最新的发布版本,下载对应的压缩包。


解压后将 dll 文件和 json 文件拷贝至反重力 Antigravity.exe 程序同目录下,可通过右键单击桌面图标快捷方式。快速跳转至该目录


然后修改 config.json 文件,将端口改为你自己使用的代理工具的端口即可,作者还在 github 上贴出了常用代理软件的端口,大大点赞。

注意:
如果复制这两个文件,启动反重力失败,需要下载 安装 微软常用运行库合集,作者提供了一键下载地址。作者 github 已经提供了详细的教程。

经测试,在 cursor 中也可使用,同样将两个文件复制到 cursor.exe 同目录下,修改 config.json 即可。cursor 中使用的 json 如下:

总结

{
“proxy”: {
“host”: “127.0.0.1”,
“port”: 10808,
“type”: “socks5”
},
“child_injection”: true,
“target_processes”:
}

对于 WSL 环境。作者也提供了替代方案,非常 nice.
有兴趣的老友可以尝试一下,如果觉得好用,给作者一个 star。


📌 转载信息
原作者:
cainiaoxue
转载时间:
2026/1/14 17:47:14

如题

前提是过了 github 的学生认证,前前后后用了一个月吧,但实际沟通就 5 次左右,今天解决了,写一下我的账号的情况

学生认证过了,但是 Azure 官网不显示我的学生订阅,然后按照网上的教程提交了工单,然后第二天就有人联系我了,微软那边告诉我我的账号订阅生效了,但是没有显示

解决办法

  1. 我有两个邮箱,一个教育的,一个 163 邮箱是注册 github 的,

  2. 客服让我先登录微软,用的 163 的邮箱,按下图点击编辑账户信息看是否有绑定 github 的信息,如果有就删掉

  3. 打开 github 的设置 -> 邮箱,保证只有一个邮箱,就是你的教育邮箱

  4. 我是用 163 邮箱注册的 Azure 账号,点击右上角头像,点击切换目录,如果你又多个目录,可以切换试一下,我切换了另一个就有订阅了

有没有佬知道这个页面怎么弄出来,订阅成功后就一直不显示了 (刚成功那会我还看了一眼,可用额度显示 701, 是不是出 bug 了,现在想再看不知道咋看了)

接下来可以用它干点啥呢,有没有佬提提建议


📌 转载信息
原作者:
xiaowang_key
转载时间:
2026/1/14 17:47:12

从 antigravity 发布以来在 wsl2 环境下访问浏览器一直报错 “Error during tool execution”。刚开始还以为是 antigravity 的 bug(心想 bug 很多也不在乎这一个),过了一个多月发现还没有修好就觉得有点不对劲,刚刚尝试了一下修复好了。

一共两步:

1. 在 windows chrome 里安装 antigravity 插件 https://chromewebstore.google.com/detail/eeijfnjmjelapkebgockoeaadonbchdd?utm_source=item-share-cb

2. 将 wsl2 的网络从默认的 nat 改为 mirrored

感谢这位 reddit 上的大佬
To anyone who use Antigravity on WSL Remote : r/google_antigravity


📌 转载信息
原作者:
ikb
转载时间:
2026/1/14 17:44:34

之前写了一篇关于 SKILL 设计最佳实践的话题,感受到佬们的热情

【SKILL 设计的最佳实践】什么是 SKILL?怎么快速写一个优秀的 SKILL? - 开发调优 - LINUX DO

也感觉好多佬们都对 SKILL 设计还蛮感兴趣的,加上最近一直在对自己的 SKILL 进行调优,研究跟看了不少官方文档以及实践示例,对 SKILL 的设计略有心得,所以就在之前的基础上,把最近关于 SKILL 的进阶设计模式简单分享一下自己的理解吧。

内容绝大多数参考了官方的最佳实践文档,然后也结合自身调优的一些体验。Claude 的文档写的还是不错的,有时间的佬友们还是蛮推荐读一读的,链接就放在下面了。

技能编写最佳实践 - Claude Docs — Skill authoring best practices - Claude Docs

还是一如既往的大纲 + 一图流,方便佬们快速了解文章的大致内容以及组织形式。

1. 引子

为了方便对 SKILL 还不太了解,或者没看过上期的佬们快速理解,我举一个例子再来说明一下什么是 SKILL。

假设你是一名喜欢到世界各地旅行,你需要准备很多装备 (对应 mcp tools) 以应对不同情况,例如:

  • 登山:登山包、山地靴、登山杖、…
  • 极地:防寒服、手套、护目镜、…
  • 雨林:驱虫喷雾、开山斧、…

那么对于特定场景我们将所需的东西都打包起来叫做 "登山套装" 或者 "极地套装"。这种为了应对特殊场景需求的工具套装,Claude 官方把他叫做 SKILL

SKILL 中还包含了一些工作流(workflow),也可以理解为在特殊场景的操作手册,告诉你在遇到突发情况时应该怎么处理。

所以通过安装 SKILL 的方式可以使得模型在特定场景发挥强大的作用,同时你也可以自己根据喜好重新安排对应的 SKILL 中工具或者进行工作流的微调。

2. 渐进式披露 & 二次披露

那么很自然的想法,SKILL 这种将现成工具打包起来的做法是不是多余的呢,为什么我们不能把所有工具跟手册都带上,那么就能处理所有情况呢?

很遗憾的是目前大模型的上下文是有限的,就像如果你带上所有的工具去旅行,那么不仅很笨重(有效上下文受限),而且遇到突发情况时,从一大堆工具中找到适合的工具的难度也上升了(上下文混淆)

所以 SKILL 的设计理念的核心在于 渐进式披露

  • 将所有的工具以及手册,先放到四次元口袋中,不随身携带(不加载上下文)
  • 但是为了能快速加载所需工具,使用目录来进行快速查找

这种记录可用工具套装的纸条(包含 name 以及 description),在遇到特定场景的时候就可以加载对应的工具(MCP tools、脚本)以及手册(项目文档、工作流规范)的模式就是 SKILL 的工作原理。

而这种目录结构加载资源的方式就称为 渐进式披露

“二次披露” 实在 渐进式披露 的基础上的进阶应用,可以进一步提高上下文的有效利用,避免宝贵的上下文资源浪费,为此 Claude 官方文档中提出三种模式来指导二次披露的使用

2.1. 模式一: 附参考资料的高级指南

同样以登山包为例,在一个 SKILL 中可以只包含基础工具以及新的索引,当基础工具不能应对情况的时候,可以再根据索引的信息加载高级工具来处理问题。

以官方示例为例:


---

name: pdf-processing

description: Extracts text and tables from PDF files, fills forms, and merges documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.

---

# PDF Processing ## Quick start

Extract text with pdfplumber:

```python

import pdfplumber

with pdfplumber.open("file.pdf") as pdf:

text = pdf.pages[0].extract_text()

```
## Advanced features **Form filling**: See [FORMS.md](FORMS.md) for complete guide **API reference**: See [REFERENCE.md](REFERENCE.md) for all methods **Examples**: See [EXAMPLES.md](EXAMPLES.md) for common patterns

这个示例中对于进阶的需求,并没有直接披露到 SKILL.md 的 body 中(就是 See xxx 这些内容),因此在 SKILL 调用的时候,这些额外的进阶内容并不会直接加载到上下文中挤占有限的上下文空间。

而当模型结合 SKILL 以及用户问题的时候,感知到可能需要 API reference 的时候,他也具备进一步读取 REFFERENCE.md 文档的能力,来补足上下文。这种二次披露的能力可以进一步使得 SKILL 的上下文得到更有效的利用。

在我的实践中还发现这种能力实际上可以诞生一种能力,我暂时将其称之为 sub-skill,具体的设计以及实现可以等之后发布了我的 SKILL 之后再详细介绍一下 嘻嘻

2.2. 模式二:领域特定组织的加载

官方文档的说法是涉及多个领域的技能,应按领域组织内容,避免加载无关的上下文。

例如,当用户询问销售指标时,Claude 只需要读取与销售相关的模式,而无需读取财务或市场营销数据。这样可以降低令牌使用量,并专注于上下文。
预先将相对独立的上下文进行分开存储,并且使用关键词来进行索引以及引导,当需要特定领域的上下文细节的时候 使用 grep 等工具实现上下文的全量加载

这实际上就是通过 文件组织 的形式,实现不同场景参考资料的分离,因此也是一种二次披露,每次只披露与使用者问题相关的场景上下文来补足信息。

与模式一不同的点在于:

  • 模式一是 根据功能 进行区分,
  • 模式二是根据 领域场景 进行区分

本质上都是对信息的有效整合以及分离调度,这里最需要学习的实际上是他参考资料的文件组织路径形式,将同类的参考进行归档,方便后续修改或者变更,也方便模型进行索引。

bigquery-skill/
├── SKILL.md (overview and navigation)
└── reference/
├── finance.md (revenue, billing metrics)
├── sales.md (opportunities, pipeline)
├── product.md (API usage, features)
└── marketing.md (campaigns, attribution)
# BigQuery Data Analysis ## Available datasets **Finance**: Revenue, ARR, billing → See [reference/finance.md](reference/finance.md) **Sales**: Opportunities, pipeline, accounts → See [reference/sales.md](reference/sales.md) **Product**: API usage, features, adoption → See [reference/product.md](reference/product.md) **Marketing**: Campaigns, attribution, email → See [reference/marketing.md](reference/marketing.md) ## Quick search Find specific metrics using grep: ```bash
grep -i "revenue" reference/finance.md
grep -i "pipeline" reference/sales.md
grep -i "api usage" reference/product.md
```

2.3. 模式三:条件细节

这一点就更好理解了,就是在引用资源前添加条件,类似通过使用 For xxx 或者是 if xxx, then xxx 的方式来让模型显示的知道何时该主动二次披露相关内容

# DOCX Processing ## Creating documents

Use docx-js for new documents. See [DOCX-JS.md](DOCX-JS.md).

## Editing documents

For simple edits, modify the XML directly.

**For tracked changes**: See [REDLINING.md](REDLINING.md)
**For OOXML details**: See [OOXML.md](OOXML.md)

2.4. 反模式:避免深度嵌套引用

既然有二次披露,那么三次四次甚至更多次披露是不是能更省上下文呢?

Claude 的回答是:不!

原因在于虽然 Claude 具备嵌套引用,但是本身对于嵌套引用采取了 额外的限制 ,例如只读取前 100 行的内容(head -100),因此嵌套引用可能会导致 信息缺失 以及 逻辑结构混乱 的现象。

官方推荐的做法是最好只调用到 二次披露 ,即只在 SKILL.md 中进行索引,不要在更深的文件结构下进行索引的创建以及链接。

注意事项:

  • 对于二次披露的参考文献应当限制长度(行数小于 100 行,因为 claude 可能只读前 100 行)
  • 如果超过 100 行,建立目录来让 claude 具备感知全文内容的能力
  • PS: 但如果目录超 100 行那就无话可说了 哈哈哈


文章正文就先写到这吧 哈哈哈(下面是一些自己的感悟以及碎碎念

总的来说 SKILL 并不是一个非常高深或者难的技术,甚至对计算机没有一点基础的小白也能迅速上手,但是更多的如果想要设计一个非常优秀的 SKILL 还是需要对这些底层的调用机制有一定了解的。

笔者在自己构建 SKILL 的过程中发现,虽然 skill-creator 这个官方工具能快速搭建一个大致框架,但是内容的详细程度还是没有想象的那么优秀,很多都还蛮简单的,而且设计模式并没有很好的贯彻最佳实践中的要点,特别是二次披露以及文件组织,常常因为获取的内容跟信息太少而没办法有效组织。

而在后续修改的过程中,如果设计者不了解最佳实践的基础原理,由于 Claude Code 谄媚的现象,往往会导致 SKILL 的后续修改反而脱离了最佳实践的要求,导致性能变差。

顺便预告一下,下次更新可能会介绍 SKILL 设计中 工作流以及反馈循环 怎么设计以及调优的相关内容。

佬们周中愉快 哈哈哈


📌 转载信息
原作者:
guanhuhao
转载时间:
2026/1/14 17:43:27

最近被叫去帮小老板代上一节大一的思政课(话说,为什么我一个 AI 方向的博后要做这种事情 - -),选题选了浅淡辩证唯物主义的认识论,主要从 教员 文章 《人的正确思想是从哪里来的》展开,结合 AI 革命的时代背景,讨论青年如何借助 AI 学习与自我提升、如何积极实践,以及如何再用实践经验改造方法,最终形成自我提升的闭环。

在用 gemini3-pro-image 做 ppt 的时候,碰巧发现智谱发布了新的 image 模型。并宣称在多项关于文字渲染的 benchmark 上达到了 SOTA,于是我就起了尝试的心思。具体宣称指标如下:

先说结论,在经过了多轮测试后发现,在模型体量相近的情况下,智谱新模型的文字渲染能力确实还行,但离 gemini-3pro-image-2k/4k 还是有一些距离,且需要更多的提示词来告知图像生成的细节才能达到较好的效果,没有 gemini-3-pro-image,那种用简短的提示词就能生成让人眼前一亮效果的能力。可能并不适合用于制作 PPT。放两张控制变量下,不同模型的文生图让大家参考一下:
GLM-IMAGE:


Gemini-3-pro-image:


📌 转载信息
原作者:
AlexChu1996
转载时间:
2026/1/14 17:42:29

最近在用 iFlow 跑本地 AI / Agent,发现只能在终端里用,
于是写了一个 Android 客户端,可以用手机远程控制本地 iFlow。

这个项目主要是:

  • iFlow 运行在 Linux / 本地机器上
  • 通过 ACP(Agent Communication Protocol)启动服务
  • Android 作为客户端,通过网络连接并交互

启动方式很简单:

iflow --experimental-acp --port 8090

然后 Android 端连接本机或局域网 IP 即可。

项目已开源:

目前是一个可用的基础版本,适合:

  • 在 Linux 上跑 iFlow 的用户
  • 想用手机随时发 prompt / 看回复
  • 对本地 AI、Agent、Android 客户端感兴趣的人

欢迎 issue / PR,一起完善。




📌 转载信息
原作者:
lamb031226
转载时间:
2026/1/14 17:41:58

安装简单,git 下来之后直接 install 就好,里面有安装命令。

因为本人通过反重力反代可以使用 claude 另外加入了 gpt team, 有了三个都可以用的编程工具,感觉荒废不太好,就各取所长。本人比较喜欢反重力的 ui 和计划模式,能够掌控全局,而他的 planning 模式像是产品经理,而 cc 和 cx 是很好的执行者,于是尝试了这个工作流,让他能够创建计划后让用户审批,然后他会让 cx 或者 cc 去执行,最后他自行审批,然后让用户判断是否达标。
欢迎大家使用尝试并且反馈问题。


📌 转载信息
原作者:
N1nEmAn
转载时间:
2026/1/14 17:41:39

基于开源项目 homepage

使用 AI 工具辅助增加了点自己觉得还行的修改

  • 去掉了 tab 及服务书签的背景,组件没有用到,应该还是有默认磨玻璃背景的
  • 增加了动态背景 bing 壁纸获取,需要自定义的,各位大佬们自己实现吧哈哈
  • 添加了 APlayer 音乐播放,纯属瞎玩.. 哈哈
  • 鼠标悬停显示 url

效果展示


代码是 AI 的,成果是各位大佬的,有需要的佬配置自取
homepage 配置

首次发帖,战战兢兢,佬友们轻点喷…


📌 转载信息
原作者:
msxiaoice
转载时间:
2026/1/14 17:41:24

OpenAI 的网页版一直存在一个 bug,当点击复制按钮时,复制出的公式内容会进行错误的转义。

正确的 Markdown 格式ChatGPT 复制出来的结果
\[ f(x) = \frac{1}{x} \][ f(x) = \frac{1}{x} ]
\( a = \frac{3}{5} \)( a = \frac{3}{5} )

然后,我就借助 Antigravity 自带的浏览器控制功能,对这个 bug 进行了逆向分析

分析结果是:

  1. ChatGPT 页面中,Message 对象存储的原始 Markdown 是完整的,形如 "\\( a=\\frac{3}{5} \\)"
  2. 在点击复制时,触发一个名为 copyToClipboard 的函数
  3. 该函数会调用一个 stripEscapes() 函数,手动进行 转义符清洗,例如把 \# 变成 #
  4. 错误就出在这里,它也会错误地将 \[ 变成 [,导致复制出的 LaTeX 出问题

找到了错误,修正就很简单,这里直接搜索 React 组件的原始数据,找到 markdown 后直接复制即可。

我也是让 Antigravity Opus 写了个油猴脚本,先将原始的复制事件拦截,然后替换成正常的,效果很好。如果有更多需求,佬友们可以自行二次开发。

// ==UserScript== // @name         ChatGPT LaTeX 复制修复 // @namespace    https://github.com/theigrams // @version      1.0.0 // @description  直接从 React 状态读取原始 Markdown,绕过 ChatGPT 的错误转义逻辑 // @author       Antigravity // @match        https://chatgpt.com/* // @match        https://chat.openai.com/* // @icon         https://chat.openai.com/favicon.ico // @grant        none // @run-at       document-end // ==/UserScript==

(function () { "use strict";

  /**
* 从 React Fiber 中提取原始 Markdown
* @param {HTMLElement} turnElement - conversation-turn 元素
* @returns {string} 原始 Markdown 文本
* @throws {Error} 如果无法读取 React 状态
*/
function getOriginalMarkdownFromReact(turnElement) { // 获取 React Fiber 节点 const fiberKey = Object.keys(turnElement).find((k) => k.startsWith("__reactFiber$") ); if (!fiberKey) { throw new Error("无法找到 React Fiber 节点"); } let fiber = turnElement[fiberKey]; let depth = 0; const maxDepth = 50; // 向上遍历 Fiber 树,查找消息数据 while (fiber && depth < maxDepth) { const props = fiber.memoizedProps; if (props) { // 尝试多种可能的数据路径 // 路径 1: message.content.parts if (props.message?.content?.parts) { const parts = props.message.content.parts; if (Array.isArray(parts) && parts.length > 0) { console.log("[Markdown Copy] 找到数据路径: message.content.parts"); return parts.join("\n"); } } // 路径 2: displayParts if (props.displayParts) { const parts = props.displayParts; if (Array.isArray(parts) && parts.length > 0) { // displayParts 可能是对象数组 const text = parts .map((p) => (typeof p === "string" ? p : p.text || "")) .join(""); if (text) { console.log("[Markdown Copy] 找到数据路径: displayParts"); return text; } } } // 路径 3: text 属性 if (typeof props.text === "string" && props.text.includes("\\")) { console.log("[Markdown Copy] 找到数据路径: text"); return props.text; } // 路径 4: content 字符串 if (typeof props.content === "string" && props.content.includes("\\")) { console.log("[Markdown Copy] 找到数据路径: content"); return props.content; } // 路径 5: children 中的文本 if (props.children?.props?.message?.content?.parts) { const parts = props.children.props.message.content.parts; if (Array.isArray(parts) && parts.length > 0) { console.log( "[Markdown Copy] 找到数据路径: children.props.message.content.parts" ); return parts.join("\n"); } } } fiber = fiber.return; depth++; } throw new Error(`遍历了 ${depth} 层 Fiber 节点,未找到原始 Markdown 数据`); } /**
* 深度搜索 React 状态中的 Markdown 内容
* @param {HTMLElement} turnElement
* @returns {string}
*/
function deepSearchMarkdown(turnElement) { const fiberKey = Object.keys(turnElement).find((k) => k.startsWith("__reactFiber$") ); if (!fiberKey) { throw new Error("无法找到 React Fiber 节点"); } const visited = new Set(); let result = null; function search(obj, path = "", depth = 0) { if (depth > 20 || !obj || visited.has(obj)) return; if (typeof obj !== "object") return; visited.add(obj); // 检查是否是我们要找的数据 if (Array.isArray(obj) && obj.length > 0 && typeof obj[0] === "string") { const text = obj.join("\n"); // 检查是否包含 LaTeX 定界符(正确的格式) if ( text.includes("\\[") || text.includes("\\(") || text.includes("\\begin") ) { console.log(`[Markdown Copy] 深度搜索找到数据: ${path}`); result = text; return; } } if (typeof obj === "string" && obj.length > 50) { if ( obj.includes("\\[") || obj.includes("\\(") || obj.includes("\\begin") ) { console.log(`[Markdown Copy] 深度搜索找到字符串: ${path}`); result = obj; return; } } for (const key in obj) { if (result) return; // 已找到,停止搜索 try { search(obj[key], `${path}.${key}`, depth + 1); } catch (e) { // 忽略循环引用等错误 } } } let fiber = turnElement[fiberKey]; let fiberDepth = 0; while (fiber && fiberDepth < 30 && !result) { if (fiber.memoizedProps) { search(fiber.memoizedProps, `fiber[${fiberDepth}].memoizedProps`); } if (fiber.memoizedState && !result) { search(fiber.memoizedState, `fiber[${fiberDepth}].memoizedState`); } fiber = fiber.return; fiberDepth++; } if (!result) { throw new Error("深度搜索未找到包含 LaTeX 定界符的原始 Markdown"); } return result; } /**
* 显示提示消息
*/
function showToast(message, isError = false) { const toast = document.createElement("div"); toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: ${ isError ? "linear-gradient(135deg, #ef4444 0%, #dc2626 100%)" : "linear-gradient(135deg, #10a37f 0%, #1a7f64 100%)" }; color: white; padding: 12px 20px; border-radius: 8px; font-size: 14px; font-weight: 500; z-index: 10000; box-shadow: 0 4px 12px ${ isError ? "rgba(239, 68, 68, 0.3)" : "rgba(16, 163, 127, 0.3)" }; animation: slideIn 0.3s ease; `; const style = document.createElement("style"); style.textContent = ` @keyframes slideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = "0"; toast.style.transform = "translateY(10px)"; toast.style.transition = "all 0.3s ease"; setTimeout(() => toast.remove(), 300); }, 3000); } /**
* 拦截复制按钮点击
*/
function interceptCopyButtons() { document.addEventListener( "click", async (e) => { // 检查是否点击了复制按钮 const btn = e.target.closest( 'button[data-testid="copy-turn-action-button"]' ); if (!btn) return; // 阻止原始的复制行为 e.stopPropagation(); e.preventDefault(); console.log("[Markdown Copy] 拦截到复制按钮点击"); try { // 找到对应的消息容器 const turn = btn.closest('[data-testid^="conversation-turn-"]'); if (!turn) { throw new Error("无法找到消息容器元素 (conversation-turn)"); } console.log( "[Markdown Copy] 找到消息容器:", turn.getAttribute("data-testid") ); // 尝试从 React 状态读取原始 Markdown let markdown; try { markdown = getOriginalMarkdownFromReact(turn); } catch (e1) { console.log("[Markdown Copy] 常规路径失败,尝试深度搜索..."); markdown = deepSearchMarkdown(turn); } if (!markdown) { throw new Error("获取到的 Markdown 为空"); } // 写入剪贴板 await navigator.clipboard.writeText(markdown); console.log("[Markdown Copy] 成功复制原始 Markdown"); console.log( "[Markdown Copy] 预览:", markdown.substring(0, 200) + "..." ); showToast("✓ 已复制原始 Markdown"); } catch (error) { console.error("[Markdown Copy] 错误:", error); showToast(`✗ 复制失败: ${error.message}`, true); // 不 fallback,直接报错 throw error; } }, true ); // 使用捕获阶段,确保先于其他处理器执行 } // 初始化 console.log("[Markdown Copy] 脚本已加载 v1.0.0"); interceptCopyButtons(); console.log("[Markdown Copy] 复制按钮拦截已启用"); })();

📌 转载信息
原作者:
Theigrams
转载时间:
2026/1/14 17:40:35

前排声明:考虑到大家观感,文章经过 AI 润色排版。

前段时间美团月卡的推广返现力度很大,我也跟风注册了美团联盟。但在实际推广中遇到了一个巨大的痛点

  • 别人的推广:直接分享二维码海报,用户在微信扫码后自动跳转小程序,体验丝滑。

  • 我的推广:美团联盟小程序里只给了一串口令,用户需要 “复制口令 → 打开美团 App → 弹窗跳转”,步骤极其繁琐,严重影响转化率。

全网搜索 “口令转链接” 无果后,我尝试求助 Gemini,没想到真把这事儿搞定了!以下是全流程复盘:

第一步:逆向分析竞品二维码

首先,我找了一张别人分享的可以直接跳转的二维码海报,进行解码。 得到了一串看起来很乱的链接: http://i.meituan.com/wrapapi/qrcode/pt?url=%2Findex%2Fpages%2Fh5%2Fh5%3Fweburl%3Dhttps%253A%252F%252Fclick.meituan.com%252Ft%253Ft%253D1…

问题:这串链接直接在微信内点击是无法打开的。

第二步:AI 介入分析

我将链接投喂给 Gemini,它迅速给出了关键分析:

Gemini 的洞察: 这个链接是美团用于唤起微信小程序的中间层接口数据。 之所以无法直接点击打开,是因为其中的核心参数是小程序内部路径(相对路径),而非标准的 HTTP 网页链接。微信的 “扫一扫” 内置了特定解析逻辑,能识别并跳转,但直接点击不行。

Gemini 帮我将链接拆解为两部分:

  • 外壳(唤起接口)http://i.meituan.com/wrapapi/qrcode/pt?url=...

  • 内核(核心推广参数)click.meituan.com/t?t=1...

结论:只要能拿到属于我自己的 “内核” 链接,套进 “外壳” 里,就能生成二维码!

第三步:抓包获取核心参数

由于美团 App 的月卡购买界面没有直接的分享按钮(无法直接提取链接),我只能进行抓包操作。

  • 抓包过程:搜索 click 关键词无果。

  • Gemini 提示:尝试搜索 html 相关请求。

  • 成功定位:在 Gemini 的建议下,我成功抓到了两个链接,其中一个正是包含返利参数(Aff/P 值)的关键链接:

    https://click.meituan.com/t?p=oen2XLxziUb…
    
    

第四步:合成与验证

最后,Gemini 帮我生成了最终的组合链接格式。我将其转成二维码,找朋友实测购买。

结果:微信扫码直接跳转,后台成功收到返利!


📌 转载信息
原作者:
d.to
转载时间:
2026/1/14 17:40:24

大家好,我是 Bin ,LoopCare 的独立开发者。

为什么做这个 App ?

作为一个轻度强迫症患者,市面上大多数 To-Do 应用让我感到焦虑。生活中的琐事(如猫咪驱虫、更换电动牙刷头、植物浇水)并不需要精确到“某年某月某日几点”。一旦我因为忙碌错过了打卡,看见满屏红色的“已过期”,反而会让我产生挫败感,最终选择放弃记录。

所以我开发了 LoopCare

它的核心理念是 “弹性周期”“上次是何时”
比如设置“每 14 天换床单”,如果你晚了 3 天才换,没关系,下一个周期会自动从你实际完成的那天开始顺延。生活不是考试,不应该有扣分。

技术与设计 (Talk is cheap, show the code)

  • 纯原生 SwiftUI 开发:为了追求极致的丝滑离手动画和响应速度。
  • CloudKit 同步:数据在 iPhone 和 iPad 间无感漫游。
  • Core Data 离线优先:在这个连备忘录都要联网的时代,我坚持所有数据通过 Core Data 本地优先存储。
  • 隐私至上:App 不收集任何用户数据。

Loop_Care.png

关于收费

App 免费下载,可体验核心功能(限制 5 个任务)。
一次性内购解锁 Pro (无限制任务 + iCloud 同步 + 图标库),无订阅制,一杯咖啡钱终身买断。

V 友福利

为了感谢 V 站大家的支持,这里提供 10 个 Pro 兑换码。

67M97A4HK94E
K3LH3EW69JYP
H6M4X7WFAT4L
HAH4P9KYR94X
MT9HE6HTEPRM
PANTFMXNX6AF
WM6REN9NTNY7
XYKHXP69YAL4
M7FF6JKKX4TR
PJKNRKFFF7EW

App Store 传送门: https://apps.apple.com/cn/app/loopcare-%E5%BC%B9%E6%80%A7%E5%91%A8%E6%9C%9F%E4%B9%A0%E6%83%AF%E4%B8%8E%E7%94%9F%E6%B4%BB%E8%AE%B0%E5%BD%95/id6757657981

(如果您觉得 App 不错,希望能去 App Store 给个好评,这对我这种冷启动的 App 真的太重要了 🙏)

Coding agents(编码智能体) 已成为应用型 AI 中最活跃的领域之一,但许多团队在模型或服务商更迭时,仍不断重复构建脆弱的基础设施。那么,如何在生态不断变化的背景下保持快速迭代与高度韧性,并将更多精力投入到领域特定的工作流程和用户体验上?

作为行业内的动向标杆,OpenAI 的 Codex 提出了解决方法——“模型和 Harness(工具集)的共同构建”。最近,OpenAI 的架构师 Bill Chen 和 Brian Fioca 在演讲里一起详细介绍了该构建过程中克服的挑战,以及这个 Coding Agent 本身一些新兴的使用模式。基于该演讲视频,InfoQ 进行了部分删改。

核心观点如下:

  • 通过将模型与 Harness 一同开发,你能更好地理解它的行为,这也是 Codex 作为一个集成了模型和 Harness 的系统的优势所在。

  • 单纯在模型上构建包装器,忽视了基础设施层的整体价值。将精力集中在让产品脱颖而出的差异化功能上,才是这种模式的核心价值所在。

  • 未来将是关于庞大代码库和非标准库的时代,如何在闭源环境中工作,如何匹配现有模板和实践,模型将不断支持这些能力。

Coding Agent 的构成

首先,我们来谈谈 Coding Agent 的构成。其实非常简单,一个 Coding Agent 由三部分组成:用户界面、模型和 Harness。用户界面显而易见,可能是命令行工具,也可能是集成开发环境,或者是云端或后台 Agent。模型也很直白,比如我们最近发布的 GPT-5.1 系列模型或其他一些供应商的模型。至于 Harness,这是一个稍微复杂一点的部分,它直接与模型交互,最简化地说,可以将其看作是由一系列提示和工具组合而成的核心 Agent 循环,它为模型提供输入和输出。

Coding 领域是应用人工智能最活跃的前沿之一,而随着新模型的不断发布,我们面临的挑战也在增加。更为复杂的是,大家不得不不断调整 Agent 以适应新发布的模型。

接下来我们将聚焦于 Harness 的部分。Harness 是模型的接口层,它是模型与用户、代码之间进行交互的媒介。它包括了模型需要的所有组件,以便在多轮对话中进行工作,调用工具,并最终为你编写代码,解读用户的需求。对一些产品来说,Harness 可能是其中的关键部分。不过,构建一个高效的 Harness 并不是一件轻松的事。

那么,构建 Harness 过程中遇到的挑战有哪些呢?首先是 AV(音视频工具)问题。你可能会为 Agent 提供一个全新的、创新的工具,但它可能是模型之前从未见过的,它可能并不擅长使用这种工具。即使它曾经见过,你也需要花时间根据该模型的特点调整 Prompt。

新模型不断发布,延迟问题也是一个挑战。模型在处理某些问题时需要时间,那么,我们应该如何设计提示,避免延迟过长?如何在用户体验上展示模型思考的过程?它在思考时是否与用户沟通,还是我们需要总结其输出结果?此外,管理上下文窗口和数据压缩也是一大难题。另外,API 接口也在不断变化,现在我们有完成功能、响应功能,以及未来可能出现的其他功能,模型是否能熟练使用这些工具以便发挥最大的智能也是一个问题。

将模型适配到 Harness 中需要大量的 Prompt 设计。实际上,模型的训练方式会带来一些副作用。我喜欢这样理解:(Steerability = Intelligence + Habit)智能加上习惯。一方面,智能是指:模型擅长什么?熟悉哪些编程语言?在某些框架中,模型能把代码写得多好?另一方面,它又养成了哪些习惯来解决问题?我们在训练模型时,培养了它在规划解决方案、查找背景信息、思考问题后再动手写代码,并在最后测试工作的习惯。

理解这些习惯是成为一名优秀的 Prompt 工程师的关键。如果你没有按照模型熟悉的方式来指导它,可能会遇到问题。当我们发布 GPT-5 时,许多不习惯使用我们模型的人,尝试将其他模型的 Prompt 直接套用到我们的 Harness 中,结果发现我们的模型做的事情比其他模型要更为细致,导致了响应速度慢,效果不如预期。我们最终发现,如果让模型按照它习惯的方式进行工作,而不是过度引导,它的表现会更好。通过与模型的对话,我问它:“我喜欢这个解决方案,但它花了太长时间。下次你能做得更快吗?”模型回答说:“你让我去看所有的内容,其实我并不需要这样做,正是因为这个原因,才耗费了这么长时间。”

因此,通过将模型与 Harness 一同开发,你能更好地理解它的行为,这也是 Codex 作为一个集成了模型和 Harness 的系统的优势所在。

Codex 作为 Harness/Agent

Codex 被设计成一个适用于各种编程环境的 Agent,它可以作为 VS Code 插件、CLI 工具使用,甚至可以通过 VS Code 插件或手机上的 ChatGPT 在云端调用。它的功能非常基础:你可以通过提示将想法转化为可运行的代码,具备规划能力。它能在代码仓库中导航并编辑文件,执行命令和任务,你也可以从 Slack 或 GitHub 上调用它来审查 PR。

这意味着 Codex 的 Harness 需要能够完成许多复杂的任务:需要处理并行工具调用、线程合并等问题,还要考虑安全性,例如沙箱管理、提示语转发、权限设置、端口管理等。数据压缩和上下文优化的管理也非常复杂。何时触发压缩,何时重新注入数据,如何优化缓存,所有这些都是必须要解决的挑战。如果你要从零开始构建这些功能并保持其更新,工作量巨大。幸好,我们已经将这些功能集成到一个 Agent 系统中,它能安全地编写自己的工具来解决遇到的新问题。

这听起来比普通的 Coding Agent 强大多了,不是吗?但想想看,其实在浏览器和图形用户界面出现之前,我们操作计算机的方式不就是通过命令行界面写代码并将其串联起来吗?这意味着,如果你能将任务以命令行方式以及文件任务的形式表达出来,Codex 就能知道该如何执行。

举个例子,我喜欢使用 Codex 将我的桌面上的照片整理到一个文件夹里,这是一个非常简单的应用场景。但它还能做的不仅如此,它能够分析文件夹中大量的 CSV 文件,进行数据分析,这并不一定是 Coding 任务,只要能够通过命令行工具来完成,Codex 就能帮你做。现在我们可以看到,Codex 是如此强大和有趣。

用 Codex 构建自己的 Agent

如果你希望将 Codex 集成到自己的 Agent 中,该如何操作呢?如果你打算创建下一个 Coding 初创公司,一个关键的模式是:Harness 成为新的抽象层。这个模式的好处非常明显,你不再需要在每次模型升级时都优先优化提示语和工具。但这是不是意味着你仅仅是在构建一个包装器呢?不是。正如我所说,单纯在模型上构建包装器,忽视了基础设施层的整体价值。将精力集中在让产品脱颖而出的差异化功能上,才是这种模式的核心价值所在。

我们来看看一些我们与客户合作时所遇到的模式,这些模式实际上帮助他们成功构建了产品。Codex 是一个 SDK,你可以通过 TypeScript 库来调用它,也可以通过 Python 执行它。它还提供了一个 GitHub 动作,能够自动合并 PR 中的冲突,解决大家讨厌的合并问题。此外,你还可以将它添加到 AgentSDK 中,并为你的产品提供 MCP 连接器。这样,你就可以拥有一个 Agent 系统。

我喜欢说,我们从最初的聊天机器人开始,它们能与用户对话;然后我们为这些聊天机器人提供了使用的工具;如今,你可以为聊天机器人添加更多工具,使它能够自己生成尚未拥有的 Harness。现在,你可以构建一个企业级的软件,允许它为每个客户即时编写插件连接器,这曾是专业服务团队的工作。你可以获得完全可定制的软件,且它可以与自己对话。我曾为开发日创建了一个看板,它能够自动修复自己的 bug,非常有趣。

 

最后,你也可以像 Zed 一样,将 Codex 嵌入到一个层级中,为 IDE 提供接口,使其能够与用户互动并进行代码编辑。这样,Zed 就不必处理我们擅长的部分,而是可以专注于打造最好的代码编辑器。

我们的顶级合作伙伴,如 GitHub,已经利用这些模式取得了巨大成功。我们为 GitHub 创建了一个 SDK,允许他们直接与 Codex 集成。你也可以使用这个 SDK 将 Codex 作为你 CI/CD 管道的一部分,或者将它作为与自己 Agent 直接互动的工具。如果你想定制 Agent 层,完全可以这么做。举个例子,我们与 Cursor 团队紧密合作,他们将自己的 Harness 与我们开源的 Codex CLI 实现对接,成功地优化了系统性能,所有这些都是公开可用的,你可以克隆我们的代码库,随意使用。

Codex 的未来是什么样的呢?它还没有发布一年,尤其是在推出 Codex Max 之后,变化非常迅速。它目前是增长最快的模型,每周服务数十万亿个 token,这个数字从开发日以来翻了一番。我们可以合理假设,模型将变得更强大,它们能处理更长周期的任务,而且不需要监督。新模型的信任度将进一步提高,我相信这些模型已经能够处理比六个月前更复杂的工作,而且这种信任感将不断增长。

未来将是关于庞大代码库和非标准库的时代,如何在闭源环境中工作,如何匹配现有模板和实践,模型将不断支持这些能力。SDK 也将不断发展,以更好地支持这些模型的能力,使模型能够在执行任务的过程中不断学习,避免重复错误,并为写代码和使用终端解决问题的 Agent 提供更多支持,你将能够通过 SDK 在自己的产品中使用这一切。

那么,我们从中学到了什么呢?Harness 构建非常复杂,特别是在新的模型不断发布的背景下。我们已经为你在 Codex 里构建了一个集成的工具,你可以直接使用它,或者查看源代码自行改进。除 Coding 以外,通过它你还可以构建更多全新功能,而我们会处理确保你的计算机 Agent 具备最强的能力。同时,我们非常期待看到你们用它创造出的产品。

参考链接:

https://www.youtube.com/watch?v=wVl6ZjELpBk