github 上 skills 收集
这个开源项目精选了超级多的 skills,从文档处理,工具调用,市场分析,数据分析,系统安全等 各大精选的 skill 都有。
xiaohack博客专注前沿科技动态与实用技术干货分享,涵盖 AI 代理、大模型应用、编程工具、文档解析、SEO 实战、自动化部署等内容,提供开源项目教程、科技资讯日报、工具使用指南,助力开发者、AI 爱好者获取前沿技术与实战经验。
这个开源项目精选了超级多的 skills,从文档处理,工具调用,市场分析,数据分析,系统安全等 各大精选的 skill 都有。
Kiro 是亚马逊云科技于 2025 年 7 月 16 日推出的专为 AI Agent 设计的集成开发环境(agentic IDE)。
新注册账号有 500 积分可以使用高级模型。
把 kiro 账号 添加到 KiroGate ,转为 标准的 API,配置到 CodeSwitch , 可以在 claude codex 使用
使用 google 邮箱 直接登录
这一步可以省略,因为 可以直接用 Kiro Account Manager 拉起登录
下载 Kiro Account Manager
版本 1:Eletronic 版本:https://github.com/chaogei/Kiro-account-manager,100M+
版本 2:Rust 版本:https://github.com/hj01857655/kiro-account-manager,<5M
安装 Kirogate
docker run -d -p 8000:8000 \
-e PROXY_API_KEY="your-password" \
-e ADMIN_PASSWORD="your-admin-password" \
-e ADMIN_SECRET_KEY="your-random-secret" \
-v kirogate_data:/app/data \
--name kirogate \
ghcr.io/awei84/kirogate:main
为什么要这个软件》》》》》最主要的原因是自动刷新 token,免得账号过期。
支持多种方式添加账号
GitHub - aliom-v/KiroGate: OpenAI & Anthropic 兼容的 Kiro IDE API 代理网关,支持 Claude Code CLI
docker run -d -p 8000:8000
-v kirogate_data:/app/data
-e PROXY_API_KEY=
-e ADMIN_PASSWORD="aadf5beb"
-e USER_SESSION_SECRET=
-e ADMIN_SECRET_KEY=
--name kirogate ghcr.io/awei84/kirogate:main
因为只有普通用户才能添加自己的 token
注意 管理员的密码 是 启动容器使用配置的 ADMIN_PASSWORD
KirGate 提供的是隐藏的管理后台,需要手动输入路由进入
比如:http://127.0.0.1:8000/admin/login
/admin/login → 登录页面 /admin → 管理面板(需登录) /admin/logout → 退出登录
这个 token 可以在 Kiro Account Manager 复制
添加 token 的时候 可以选择公开或者私有
Kiro 的服务地址:ip:8000/v1/chat/completions
# OpenAI 格式
curl http://localhost:8000/v1/chat/completions \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "你好"}]}'
# Anthropic 格式
curl http://localhost:8000/v1/messages \
-H "x-api-key: sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{"model": "claude-sonnet-4-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "你好"}]}' 你家一个 token 我家一个 token,就变成了 token 池子了
可以用下面两个现成的。
https://linux.do/t/topic/1375741
https://ying.jhun.edu.kg/
🔐
获取 Refresh Token
🌐 方式一:浏览器获取(推荐)
1打开 https://app.kiro.dev/account/usage 并登录 2按 F12 打开开发者工具
3点击 应用/Application → 存储/Storage → Cookie 4选择 https://app.kiro.dev 5复制 RefreshToken 的值
🛠️ 方式二:Kiro Account Manager
使用 Kiro Account Manager 可以轻松管理多个账号的 Refresh Token。 昨晚开始开始入手 OpenCode ,整理了一份从零开始的安装与配置笔记,分享给各位佬友。
推荐使用 brew 安装,稳定性更高:
brew install anomalyco/tap/opencodenpm i -g opencode-ai安装完成后先输入 opencode 启动(能白嫖 GLM4.7),然后在会话中直接粘贴以下链接安装 oh-my-opencode:
Install and configure by following the instructions here https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/refs/heads/master/README.md
进阶推荐:
由于 OpenCode CLI 执行任务时会有大量 Tool Use 调用,普通 URL 转发容易协议报错。这里推荐使用 CLIProxyAPI (CPA) 进行协议转换。
建议直接走 Source Build,日志更透明:
git clone https://github.com/router-for-me/CLIProxyAPI.git
cd CLIProxyAPI
go mod download
go run main.go # 启动 坑点: 字段名必须准确,否则会静默失败。
auth-dir 而不是旧版的 credentials-directory。openai-api-keys(复数,带 s)。port: 8317 auth-dir: "/绝对路径/auth" allow-unauthenticated: true # 本地调试建议开启 openai-api-keys: - api-key: "sk-OneAPI令牌" base-url: "https://OneAPI地址/v1" models: - id: "claude-opus-4-5" # OpenCode 中显示的名称 map-to: "claude-opus-4-5-20251101" # OneAPI 后台真实 ID 修改 ~/.config/opencode/opencode.json。因为 CPA 侧开了免密,这里直接配置 Provider 即可:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"oh-my-opencode",
"@tarquinen/opencode-dcp@latest"
],
"provider": {
"anthropic": {
"options": {
"baseURL": "http://127.0.0.1:8317/v1"
}
}
}
}
此前,我曾在《[教程] 如何使用 AI 智能规划你的专属行程?》一文中分享过基于 MCP 智能生成旅游攻略的方案。当时的解决方案主要依赖 “厚重” 的提示词(Prompt)来驱动 Agent。这种方式虽然可行,但存在一个显著弊端:大量的 Context(上下文)窗口被提示词本身占用,导致实际处理任务的上下文空间被浪费,且 Token 消耗巨大。
为了解决上述问题,在深入研究了 SKILL 机制后,我调整了技术思路,采用了 SKILL + MCP 的组合架构。通过将复杂的指令逻辑封装为 SKILL,减轻了 Prompt 的负担,从而释放了更多的上下文空间给实际业务数据。
经过测试,在新架构下生成一篇简单的旅游攻略,Token 消耗成功控制在了 169.7k 左右,相比纯 Prompt 驱动方案有了显著优化。
目前该方案的 SKILL 实现已上传至 GitHub,欢迎参考: SKILL 地址: QianJue-CN/TravePlanHelper
当然,目前的 SKILL 实现尚不完善,对于上下文的精细控制和 Token 消耗的极致优化也仅仅是一个开始。本文旨在抛砖引玉,分享一次技术探索的尝试,希望能得到各位佬友的指正与认可。
杭州 - 千岛湖周末情侣游攻略.pdf
最近购买了一台龙芯架构的电脑,苦于没有支持龙芯架构的 ssh 软件,偶然发现了它–Finalshell。界面非常复古但是功能强大,更可贵的是似乎一直在更新,更更可贵的是居然支持国产的龙芯架构,格局一下子打开了好吗
推荐给各位佬友试试。
官网地址:
https://www.hostbuf.com/t/1081.html
今天给佬们分享一个自己在玩的项目,不同于市面上现有的大部分 AI 项目给出实操投资,我们这个项目更加偏向于只是收集市场数据,给出盘前盘后的看法,方便大家有个基础的参考。
技术栈:
python + TypesScript
数据来源:
Tavily + AKSHARE
AI: GEMINI 2.5pro
仪表盘:查看一些盘面大数据
基金池:添加我们关心的基金,可以配置定时任务生成盘前盘后的数据
股票:和上面的基金池一样
情绪:主要是针对市场的数据进行复盘,风格比较喷子
情报:就是展示我们生成的盘前 / 盘后的报表
商品:目前只实现了针对黄金和白银分析
系统配置:就是我们配置 AI 和 Tavily 联网搜索的页面
目前仍在更新…
仓库地址:GitHub - Austin-Patrician/eastmoney
[Scriptable] NASA 每日天文图 (APOD) iOS 小组件
一个在 iPhone 桌面上看 NASA 的每日天文一图 (APOD)。
核心亮点
零门槛 (Zero Config):不需要申请 NASA API Key。直接抓取 NASA 官网数据,省去注册麻烦,也不用担心 Key 额度超限。
自动取色 (Dynamic Color):脚本会自动提取当日图片的主色调,生成磨砂质感的渐变背景蒙版。
哪怕图片是黑白的,组件也不会单调。
超强抗网络波动:
智能选图:自动识别官网主图,排除 Logo 和图标。
多重兜底:直连 NASA 失败时,自动切换到 weserv 图片代理,大幅提升国内加载成功率。
缓存优先:断网或请求失败时,自动展示上一次缓存的图片,绝不开天窗(不黑屏)。
UI 细节:
底部半透明信息卡片,模拟 iOS 原生组件质感。
顶部自动根据主色调生成 “APOD” 胶囊标签。
遇到 “视频日”(当天是视频没图片),会自动显示提示,并保持美观的背景。
预览图
使用方法
App Store 下载 Scriptable。
打开 App,点击右上角 + 号,新建脚本。
将下方代码完整复制粘贴进去,命名为 NASA APOD。
回到桌面,添加 Scriptable 小组件,尺寸选 中号 (
在小组件设置里,Script 选择刚才保存的脚本即可。
脚本代码
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-blue; icon-glyph: star;
/*
* APOD 零 Key 美化稳定版(不显示在线状态)
* - 数据源:https://apod.nasa.gov/apod/astropix.html
* - 主图优先:href="image/..." -> img src="image/..."
* - 图片兜底:直连失败 -> weserv 代理
* - 防黑缓存:缓存过小图会自动丢弃
*/
const LOCALE = "zh-CN";
const HTML_TIMEOUT_SEC = 25;
const IMAGE_TIMEOUT_SEC = 45;
const PREVIEW_SIZE = "medium "; // small | medium | large
// 如果你之前已经黑了很多次,强烈建议把它改成 true 跑一次,再改回 false
const RESET_CACHE_ONCE = false;
const fm = FileManager.local();
const cacheDir = fm.joinPath(fm.documentsDirectory(), "apod_nokey_widget_cache");
const cacheJsonPath = fm.joinPath(cacheDir, "apod.json");
const cacheImgPath = fm.joinPath(cacheDir, "apod.jpg");
function ensureCacheDir() {
if (!fm.fileExists(cacheDir)) fm.createDirectory(cacheDir);
}
function resetCacheIfNeeded() {
if (!RESET_CACHE_ONCE) return;
try { if (fm.fileExists(cacheJsonPath)) fm.remove(cacheJsonPath); } catch {}
try { if (fm.fileExists(cacheImgPath)) fm.remove(cacheImgPath); } catch {}
}
async function requestText(url, timeoutSec) {
const req = new Request(url);
req.timeoutInterval = timeoutSec;
const text = await req.loadString();
return { text, statusCode: req.response?.statusCode };
}
async function requestImage(url, timeoutSec) {
const req = new Request(url);
req.timeoutInterval = timeoutSec;
const img = await req.loadImage();
return img;
}
function readCache() {
try {
if (!fm.fileExists(cacheJsonPath)) return null;
const wrapper = JSON.parse(fm.readString(cacheJsonPath));
const img = fm.fileExists(cacheImgPath) ? fm.readImage(cacheImgPath) : null;
return { data: wrapper.data, img };
} catch {
return null;
}
}
function writeCache(data, img) {
ensureCacheDir();
fm.writeString(cacheJsonPath, JSON.stringify({ savedAt: Date.now(), data }));
if (img) fm.writeImage(cacheImgPath, img);
}
function stripHtml(s) {
return String(s || "").replace(/<[^>]*>/g, "").replace(/\s+/g, " ").trim();
}
function absApodUrl(pathOrUrl) {
if (!pathOrUrl) return null;
const base = "https://apod.nasa.gov/apod/";
const raw = String(pathOrUrl).trim();
if (/^https?:\/\//i.test(raw)) {
// 强制 apod 走 https
return raw.replace(/^http:\/\/apod\.nasa\.gov\//i, "https://apod.nasa.gov/");
}
return (base + raw.replace(/^\//, "")).replace(/^http:\/\/apod\.nasa\.gov\//i, "https://apod.nasa.gov/");
}
function pickMainImageUrl(html) {
// 1) 最稳:主图通常包在 <a href="image/...jpg"><img ...></a>
const href = html.match(/<a[^>]+href="(image\/[^"]+\.(?:jpg|jpeg|png|webp)(?:\?[^"]*)?)"/i);
if (href?.[1]) return absApodUrl(href[1]);
// 2) 次稳:直接 img src="image/..."
const src = html.match(/<img[^>]+src="(image\/[^"]+\.(?:jpg|jpeg|png|webp)(?:\?[^"]*)?)"/i);
if (src?.[1]) return absApodUrl(src[1]);
// 3) 兜底:找任意 image/xxx.jpg
const any = html.match(/(image\/[^\s"'<>]+\.(?:jpg|jpeg|png|webp))/i);
if (any?.[1]) return absApodUrl(any[1]);
return null;
}
function parseApodHtml(html) {
const bolds = [...html.matchAll(/<b>([\s\S]*?)<\/b>/gi)].map((m) => stripHtml(m[1]));
const title =
bolds.find(
(t) =>
t &&
!/Astronomy Picture of the Day /i.test(t) &&
!/APOD/i.test(t) &&
t.length >= 3 &&
t.length <= 90
) || "NASA APOD";
const dateMatch = html.match(/(\d{4}\s+[A-Za-z]+\s+\d{1,2})/);
const dateText = dateMatch ? dateMatch[1] : "";
let credit = "";
const creditMatch = html.match(/Image Credit[^<]*<\/b>\s*([\s\S]*?)<\/center>/i);
if (creditMatch?.[1]) credit = stripHtml(creditMatch[1]).slice(0, 120);
const isVideo = /<iframe\b/i.test(html) || /youtube\.com|youtu\.be|vimeo\.com/i.test(html);
const imageUrl = pickMainImageUrl(html);
return { title, dateText, credit, imageUrl, isVideo };
}
function parseToLocalDateString(maybeEnglishDate) {
if (!maybeEnglishDate) return "";
try {
const d = new Date(maybeEnglishDate);
if (!isNaN(d.getTime())) {
return d.toLocaleDateString(LOCALE, { year: "numeric", month: "long", day : "numeric" });
}
} catch {}
return String(maybeEnglishDate);
}
function weservProxy(url) {
const stripped = String(url).replace(/^https?:\/\//, "");
return `https://images.weserv.nl/?url=${encodeURIComponent(stripped)}`;
}
function isImageUsable(img) {
if (!img) return false;
// Scriptable 的 Image 通常有 size
const w = img.size?.width || 0;
const h = img.size?.height || 0;
// 主图不可能这么小;避免 logo/透明小图导致“黑”
return w >= 300 && h >= 300;
}
async function downloadMainImage(imageUrl) {
const direct = imageUrl;
const proxy = weservProxy(imageUrl);
try {
const img = await requestImage(direct, IMAGE_TIMEOUT_SEC);
if (isImageUsable(img)) return img;
} catch {}
const img2 = await requestImage(proxy, IMAGE_TIMEOUT_SEC);
if (!isImageUsable(img2)) throw new Error("下载到的图片过小(可能不是主图)");
return img2;
}
function makeOverlayGradient() {
// 不要太黑:让背景图清晰可见,同时保证底部文字清楚
const g = new LinearGradient();
g.locations = [0, 0.55, 1];
g.colors = [
new Color("#000000", 0.16),
new Color("#000000", 0.06),
new Color("#000000", 0.46),
];
return g;
}
function makeFallbackGradient() {
const g = new LinearGradient();
g.locations = [0, 1];
g.colors = [new Color("#0b1220"), new Color("#111827")];
return g;
}
function addBadge(container) {
const pill = container.addStack();
pill.setPadding(6, 10, 6, 10);
pill.cornerRadius = 999;
pill.backgroundColor = new Color("#0b1020", 0.30);
const t = pill.addText("APOD");
t.textColor = new Color("#ffffff", 0.92);
t.font = Font.semiboldSystemFont(10);
}
function addInfoCard(container, data, family) {
const card = container.addStack();
card.layoutVertically();
card.setPadding(12, 12, 12, 12);
card.cornerRadius = 16;
card.backgroundColor = new Color("#0b1020", 0.36);
const titleFont = family === "small" ? 15 : 17;
const dateFont = family === "small" ? 11 : 12;
const title = card.addText(data?.title || "NASA APOD");
title.textColor = Color.white();
title.font = Font.boldSystemFont(titleFont);
title.lineLimit = 2;
card.addSpacer(6);
const dateLine = parseToLocalDateString(data?.dateText || "");
if (dateLine) {
const d = card.addText(dateLine);
d.textColor = new Color("#e5e7eb", 0.92);
d.font = Font.systemFont(dateFont);
d.lineLimit = 1;
}
if (data?.credit) {
const c = card.addText(`© ${data.credit}`);
c.textColor = new Color("#cbd5e1", 0.85);
c.font = Font.systemFont(10);
c.lineLimit = 1;
}
if (!data?.imageUrl && data?.isVideo) {
card.addSpacer(6);
const v = card.addText("今日为视频,官网无可用图片预览");
v.textColor = new Color("#cbd5e1", 0.9);
v.font = Font.systemFont(10);
v.lineLimit = 2;
}
}
async function createWidget() {
resetCacheIfNeeded();
const cache = readCache();
let data = cache?.data || null;
let bgImg = isImageUsable(cache?.img) ? cache.img : null;
// 在线抓取
try {
const url = "https://apod.nasa.gov/apod/astropix.html";
const { text, statusCode } = await requestText(url, HTML_TIMEOUT_SEC);
if (statusCode && statusCode >= 400) throw new Error(`HTTP ${statusCode}`);
const parsed = parseApodHtml(text);
data = parsed;
if (parsed.imageUrl) {
const img = await downloadMainImage(parsed.imageUrl);
bgImg = img;
writeCache(parsed, img);
} else {
// 只缓存文字,不覆盖旧图
ensureCacheDir();
fm.writeString(cacheJsonPath, JSON.stringify({ savedAt: Date.now(), data: parsed }));
}
} catch (e) {
// 在线失败:无缓存才显示错误页
if (!data && !bgImg) {
const w = new ListWidget();
w.setPadding(16, 16, 16, 16);
w.backgroundGradient = makeFallbackGradient();
const t = w.addText("APOD 暂不可用");
t.textColor = Color.white();
t.font = Font.boldSystemFont(16);
w.addSpacer(8);
const m = w.addText(String(e.message).slice(0, 180));
m.textColor = new Color("#ffcccc");
m.font = Font.systemFont(11);
m.lineLimit = 4;
w.url = "https://apod.nasa.gov/apod/astropix.html";
return w;
}
}
const w = new ListWidget();
w.setPadding(0, 0, 0, 0);
w.refreshAfterDate = new Date(Date.now() + 60 * 60 * 1000);
w.url = "https://apod.nasa.gov/apod/astropix.html";
if (bgImg) {
w.backgroundImage = bgImg;
w.backgroundGradient = makeOverlayGradient();
} else {
w.backgroundGradient = makeFallbackGradient();
}
const family = config.widgetFamily || "medium ";
const content = w.addStack();
content.layoutVertically();
content.setPadding(14, 14, 14, 14);
addBadge(content);
content.addSpacer();
addInfoCard(content, data, family);
return w;
}
// 运行
const widget = await createWidget();
if (config.runsInWidget) {
Script.setWidget(widget);
} else {
if (PREVIEW_SIZE === "small") widget.presentSmall();
else if (PREVIEW_SIZE === "large") widget.presentLarge();
else widget.presentMedium();
}
Script.complete();
在阅读完成 https://linux.do/t/topic/1371904 的内容后,发现自己想要的不是 MCP 服务,而是 API 接口,然后就自己动手修改了一个 API 的版本。在前辈的功能基础上,增加了生图的支持。
个人目前是:配合 NAS+Cloudflared 做内网穿透使用,直接变成了随便联网使用的接口。
轻喷。
LLM Agent 的训练高度依赖多样的工具交互环境。然而,真实环境访问受限 ,LLM 模拟环境容易产生幻觉和不一致 ,而人工编写沙盒又面临成本高昂、难以扩展的难题 。
针对这一难题,我们提出了 EnvScaler —— 一个通过程序合成环境的自动化框架!利用 LLM 自动编写可执行的 Python 程序,构建成百上千个不同主题的交互式环境,并自动生成配套的任务和验证逻辑。
EnvScaler 由 SkelBuilder 和 ScenGenerator 两大核心组件组成,旨在实现环境与任务的全自动构建。
环境构建 (SkelBuilder):从文本挖掘到代码实现
主题挖掘与规划:从现有文本数据中挖掘环境主题,自动规划状态空间与工具集。
程序化实现:将规划转化为完整的 Python 程序代码。
质量保证:引入双 Agent 循环质检(Dual-Agent Inspection)机制,确保生成的环境代码质量过硬。
场景构建 (ScenGenerator):基于规则的可验证奖励
数据与任务生成:为每个环境生成对应的状态数据和挑战性任务。
验证逻辑生成:我们将任务拆解为检查列表(Checklist),并将每个检查点转换为针对环境最终状态的 Python 布尔函数。这意味着 RL 训练可以获得精准的、基于规则的、可验证的 Reward 信号,彻底告别模糊的文本反馈。
规模与实测效果:
利用 EnvScaler,我们合成了 191 个环境和约 7000 个场景。
应用到 Qwen3 模型的 SFT 与 RL 训练中,在 BFCL-v3 Multi-Turn、Tau-Bench 和 ACEBench-Agent 等基准测试上均取得了显著提升!
Qwen3-4B: BFCL-MT +12.62, Tau-Bench +7.62, ACEBench-Agent +15.27
Qwen3-8B: BFCL-MT +13.00, Tau-Bench +6.62, ACEBench-Agent +12.50
数据与代码现已全面开源!
arxiv:[2601.05808] EnvScaler: Scaling Tool-Interactive Environments for LLM Agent via Programmatic Synthesis
GitHub:GitHub - RUC-NLPIR/EnvScaler: The official implementation of "EnvScaler: Scaling Tool-Interactive Environments for LLM Agent via Programmatic Synthesis".
欢迎各位佬友尝鲜!
点点 star孩子将不胜感激!!
混了这么久社区我也是终于三级了啊 (凌晨四点升级)!这几天用 AI 糊出来了一个小玩具,可以配合站里大佬
@F-droid 的项目🎉Gemini Business 2API 来了 | 支持 Docker 一键启动! 使用 Gemini Business 的各个 gemini 模型(大香蕉随便用说是)。
所有需要用到的项目我会在帖子最后给出地址,这里需要搭建的项目为 API 反代 ,Hugging Face 镜像(也可以换成别的,例如 zeabur)和域名邮箱。
这里先给出 github 地址,具体的配置详情都在 github 中可自行查阅 希望大家可以帮我点点 star,我在这里感激不尽
现在来说说项目的具体功能:
通过 github 工作流使用配置的代理节点,域名邮箱和凭证上传地址来自动注册 Gemini Business 账号,获取凭证并上传至 2api 中,可以说是相当方便了。
代理除了常见的 HTTP/SOCKS5 代理,还额外支持 VLESS 代理(塞了个 singbox,这里建议节点用好一点,不然可能打不开网页或者接不到验证码,GitHub 的 ip 无法注册)
vless 支持两种格式,一种是正常的 VLESS URL,另一种就是 YAML 配置
格式一:VLESS URL(推荐)
vless://uuid@server:port?type=tcp&security=reality&sni=example.com&fp=chrome&pbk=xxx
格式二:YAML 配置
{ server: example.com, port: 443, uuid: xxx-xxx, flow: xtls-rprx-vision, ... }
项目设定为每 6 小时自动运行一次,一次注册两个账号并上传凭证,并且支持并发注册,最多 5 并发,这些设置可以通过修改 workflows 中的 register.yml 文件修改。
最后是用到的几位大佬的帖子和项目链接,大家可以自行查看与搭建。
众所周知,OpenAI、Anthropic 和 Google 三家的模型格式各不相同。目前主流是使用 基于 NewAPI 的中转站,在 OpenCode 的配置文件中通过自定义类型进行接入。
常见的配置如下:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"new-api": {
"npm": "@ai-sdk/openai-compatible",
"name": "NewAPI",
"options": {
"baseURL": "https://xxx/v1"
},
"models": {
"gemini-2.0-flash": { "name": "gemini-2.0-flash" }
}
}
}
}
这里存在一个潜在问题: 在这种配置下,程序实际上是在调用 /v1/chat/completions 接口。对于 Gemini 渠道的模型,请求会经过 NewAPI 的一层或多层格式转换逻辑。这不仅增加了延迟,还可能导致参数缺失或兼容性报错。
更优的解决方案: 既然部分中转站支持 Gemini 原生格式,且 OpenCode 底层基于 Vercel AI SDK,我们完全可以绕过 OpenAI 兼容层。
通过查阅 AI SDK Provider 列表,我们可以直接将 npm 包替换为原生的 @ai-sdk/google。
优化后的配置:
npm 字段: 从 @ai-sdk/openai-compatible 改为 @ai-sdk/google。baseURL: 依然指向你的中转地址。{
"$schema": "https://opencode.ai/config.json",
"provider": {
"google-native": {
"npm": "@ai-sdk/google",
"name": "Google Native",
"options": {
"baseURL": "https://your-proxy.com/v1"
},
"models": {
"gemini-2.5-flash": { "name": "gemini-2.5-flash" }
},
"anthropic-native": {
"npm": "@ai-sdk/anthropic",
"name": "Anthropic Native",
"options": {
"baseURL": "https://your-proxy.com/v1"
},
"models": {
"claude-3-5-sonnet-20241022": { "name": "claude-3-5-sonnet-20241022" }
}
}
}
}
这样,调用将直接走 Google 原生协议,省去了中间的转换逻辑,响应更迅速且功能支持更完整。针对 Claude 渠道,替换为 @ai-sdk/anthropic 也是同理。
更新到了 1.2.0 版本
集合了四个项目
可以用两边的额度,google_search 和大香蕉生图
发现一家提供免登入可用 Nano Banana 生图的站
有兴趣可以玩看看
以下是我试着生成的图片及提示词
{
“FaceReference”: {
“Mode”: “Strict face preservation”,
“Instruction”: “Use uploaded reference for exact facial features”,
“Consistency”: “Face identical across all nine frames”
},
“GridComposition”: {
“FocalLengthMix”: “35mm full-body to 85mm close-ups”,
“PoseVariety”: [
“Wide stance hands behind head”,
“Palm extended toward camera”,
“OK gesture over eye playful”,
“Chin resting in both hands”,
“Half face covered by hand”,
“Twirling with hair flowing”,
“Jumping with arms up”,
“Looking over shoulder”,
“Candid laughing”
]
},
“PersonaDetails”: {
“Subject”: {
“Type”: “Same as reference”,
“Wardrobe”: “Light beige knit crop top, high-waisted blue jeans, delicate gold necklace”,
“OverallPresence”: “Confident, radiant, approachable”
}
},
“Environment”: {
“Setting”: “Outdoor open sky”,
“Background”: “Vibrant azure sky with clouds”,
“Lighting”: {
“Style”: “Harsh high-key natural sunlight”,
“Quality”: “Crisp defined shadows”
}
},
“ImageQuality”: {
“Resolution”: “8K hyper-realistic”,
“Aesthetic”: “High-end lifestyle campaign”
},
“NegativePrompt”: [
“indoor”,
“artificial light”,
“different face”,
“altered facial features”
],
“ResponseFormat”: {
“AspectRatio”: “1:1”
}
}
偷偷说一下,目前我正在进行 APP 限免的板块申请
如果可以的话希望大家支持一下!
前言
前面已经成功搭建了苹果 CMS 影视站,详细教程查看《苹果 CMS V10 搭建教程》。在上一篇文章末尾,留了几个问题:
1、服务器配置到底如何选择
2、如何修改当前模板的网站的 Logo
3、网站首页的封面如何设置
4、模板不好看如何安装其它模板
5、如何通过域名访问网站
接下来,将逐一回答,如果有其它问题,欢迎大家进交流群一起探讨:点击进入站长破壁者交流群
1、服务器配置到底如何选择
搭建影视站服务器如何选择,正如前面说的,这取决于后续具体应用场景,这里简单说明下需要考虑的点:
如果只是学习 / 玩,在自己本地搭建即可。
如果单纯为了,搭建一个影视站,然后能够在互联网访问,则需要服务器了,对于配置其实没什么要求,1 核 1G 也能够安装。
如果想搭建一个让很多人观看的影视站,那么对服务器配置就有一定要求了,比如:用户在大陆则买大陆的服务器比较好;用户在亚太则买香港、日本的服务器;用户在海外则推荐买美国服务器。配置建议选择不低于 2 核 4G 的服务器,通常配置越高后续程序运行越流畅,视频采集速度也更快。
这里稍微展开说明下,影视站如果需要体验观感比较好,那么线路的选择就比较重要了,建议选择三网精品线路的服务器,并且线路的带宽大小也非常重要:大陆线路的带宽通常比较小;亚太次之并且价格通常比较贵;美国的三网精品服务器带宽一般都比较大性价比较高。教程中使用的服务器是 VMRack 三网精品服务器,体验非常好。
2、如何修改当前模板的网站的 Logo
登录管理后台,在系统 -> 网站参数配置中修改网站的基本信息。
访问网站首页,发现刚才设置的 logo 并没有生效,检查代码发现,logo 是固定写死的,所以我们在宝塔面板中,重新上传 logo 即可。
在网站的模板目录下,上传 logo 图片:
上传后 / 或者修改源码:
刷新官网:
可以看到 icon 与 logo 都更新了,对于有基础的朋友,就可以根据自己需求修改模板。 3、网站首页的封面如何设置 设置封面非常简单,只需要把视频推荐设置为推荐 9 即可:
点击视频编辑,上传视频的海报图,即可:
刷新官网,可以看到封面已设置:
当设置多个封面后,轮播图功能并为生效:
查看模板源码,发现轮播图功能源码在 script.js 文件实现:
打开浏览器控制台,发现并未加载轮播图的 js 文件: 当知道问题出在什么地方,解决就非常简单了,通过查看源码,发现 js 文件都在这里设置的,那么只需要把 script.js 路径加上即可:
当修改好代码后,刷新官网,此时轮播图功能就正常了。 4、模板不好看如何安装其它模板 在网上搜索 maccms10 模板,这个就不多介绍了:
当找到心仪的模板后,下载源码:
一般都会有模板安装的教程:
仿爱电影 MizhiADY 模板源码下载:仿爱电影 MizhiADY 板源码.zip
上传模板源码到网站 template 目录,解压后把 mizhiady 文件移动到 template 下即可:
在管理后台模板中,可以看见模板已上传成功:
设置网站的模板:
设置成功后,按照教程先刷新官网:
设置主题后台地址: MZADY 觅知主题,/mac.php/admin/mizhiady/mzadyset
保存后,刷新管理后台,发现左侧菜单新增 MZADY 主题,进入主题,可以进行相关设置:
至此,安装其它模板的流程就结束了,关于模板的选择全凭个人爱好了,值得注意的是,网上的模板可能存在一些广告。 5、如何通过域名访问网站 网站通过域名访问的前提是得有一个域名,如何购买域名这里就略过了。 在宝塔面板,网站列表中,点击设置:
这里填写需要访问的域名地址:
在域名服务商进行域名解析,这里以 CF 为例:
通过域名访问:
此处,域名访问成功设置。 此时,浏览器提示不安全,是因为网站未设置 SSL 证书,接下来,继续为网站设置证书。为什么给网站需要设置证书?这里简单说明下,设置证书后网站会更安全。 如何给网站设置证书呢?需要先申请 SSL 证书,这里以 VMRack 的证书为例,主要是永久免费还能自动续费。 登录 VMRack 控制台,进入 SSL 证书页面,在右上角点击申请证书:
接下来需要在域名服务商填写 CNAME 域名解析:
这里还是以 CF 为例:
填写完成后,回到控制台,点击验证解析记录:
当状态都成功后,点击申请证书:
只需要等待几分钟即可:
点击管理,即可查看证书的详情信息:
下载证书:
只需要把证书,上传到宝塔的 SSL:
把前面申请好的证书信息,分别复制 / 粘贴过来即可:
可以看到证书已上传成功:
在网站设置中,直接部署证书:
开启强制 HTTPS 访问:
访问官网,此时浏览器已经未提示不安全了:
至此,苹果 CMS 的搭建;一些简单使用;通过域名访问以及简单的源码修改等,都已简单介绍,如果对此感兴趣的朋友欢迎来,站长破壁者交流群共同探讨学习,点击进入交流群。
地址 https://kiro.endpoint.cc.cd
使用 Cloudflare Worker 绕过 CORS 限制,实现生成 Device Flow 登录链接
没有做信息存储!!
源代码:
/**
* worker.js — Kiro Manual Auth (Device Flow) Web Tool
* Full UI (no cuts): Vercel/shadcn/Inspira-ish + Acrylic hover actions
*
* ✅ Flow:
* Login -> get CID/CS -> get Device Flow link (hover: Copy | Open Link) -> background polling
* -> success modal pops with Tabs: Viewer / JSON
*
* ✅ Viewer tab:
* - Shows CID / CS + ALL keys from last successful /api/poll JSON
* - Each row hover shows acrylic blur + "Copy" (same logic as URL hover, no OpenLink)
*
* ✅ Poll logic:
* - No overlapping polls: next poll only after previous finished
* - If backend returns 400 {"error":"authorization_pending"...}, treat as Pending (yellow) not error
*
* ✅ NEW (your request):
* - Modal content is scrollable (so long tokens won't squeeze/overflow the screen)
* - Each KV value area is also scrollable (does not cut value; copy still copies full value)
*/
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const path = url.pathname;
// ===== Config =====
const OIDC = "https://oidc.us-east-1.amazonaws.com";
const PORTAL = "https://view.awsapps.com";
const START_URL = `${PORTAL}/start`;
// ===== CORS =====
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
if (request.method === "OPTIONS") {
return new Response(null, { status: 204, headers: corsHeaders });
}
// ===== UI =====
if (request.method === "GET" && path === "/") {
return new Response(renderHTML(), {
headers: {
"content-type": "text/html; charset=utf-8",
"cache-control": "no-store",
},
});
}
// ===== Helpers =====
const json = (obj, status = 200, extraHeaders = {}) =>
new Response(JSON.stringify(obj), {
status,
headers: {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
...corsHeaders,
...extraHeaders,
},
});
const proxyJson = async (targetUrl, bodyObj) => {
const r = await fetch(targetUrl, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(bodyObj),
});
const text = await r.text();
return new Response(text, {
status: r.status,
headers: {
"content-type": "application/json; charset=utf-8",
"cache-control": "no-store",
...corsHeaders,
},
});
};
// ===== API: register =====
if (request.method === "POST" && path === "/api/register") {
return proxyJson(`${OIDC}/client/register`, {
clientName: "Amazon Q Developer for command line",
clientType: "public",
scopes: [
"codewhisperer:completions",
"codewhisperer:analysis",
"codewhisperer:conversations",
],
});
}
// ===== API: device authorization =====
if (request.method === "POST" && path === "/api/device") {
let req;
try {
req = await request.json();
} catch {
req = {};
}
const { clientId, clientSecret } = req || {};
if (!clientId || !clientSecret) {
return json({ error: "missing clientId/clientSecret" }, 400);
}
return proxyJson(`${OIDC}/device_authorization`, {
clientId,
clientSecret,
startUrl: START_URL,
});
}
// ===== API: poll token (device_code) =====
// UI handles 400 authorization_pending as "Pending"
if (request.method === "POST" && path === "/api/poll") {
let req;
try {
req = await request.json();
} catch {
req = {};
}
const { clientId, clientSecret, deviceCode } = req || {};
if (!clientId || !clientSecret || !deviceCode) {
return json({ error: "missing clientId/clientSecret/deviceCode" }, 400);
}
return proxyJson(`${OIDC}/token`, {
clientId,
clientSecret,
deviceCode,
grantType: "urn:ietf:params:oauth:grant-type:device_code",
});
}
// ===== API: refresh (optional) =====
if (request.method === "POST" && path === "/api/refresh") {
let req;
try {
req = await request.json();
} catch {
req = {};
}
const { clientId, clientSecret, refreshToken } = req || {};
if (!clientId || !clientSecret || !refreshToken) {
return json({ error: "missing clientId/clientSecret/refreshToken" }, 400);
}
return proxyJson(`${OIDC}/token`, {
clientId,
clientSecret,
refreshToken,
grantType: "refresh_token",
});
}
return new Response("Not Found", { status: 404 });
},
};
function renderHTML() {
const AWS_SVG = `<svg fill="currentColor" fill-rule="evenodd" height="18" viewBox="0 0 24 24" width="18" xmlns="http://www.w3.org/2000/svg" style="flex: 0 0 auto; line-height: 1;"><title>AWS</title><path d="M6.763 11.212c0 .296.032.535.088.71.064.176.144.368.256.576.04.063.056.127.056.183 0 .08-.048.16-.152.24l-.503.335a.383.383 0 01-.208.072c-.08 0-.16-.04-.239-.112a2.47 2.47 0 01-.287-.375 6.18 6.18 0 01-.248-.471c-.622.734-1.405 1.101-2.347 1.101-.67 0-1.205-.191-1.596-.574-.39-.384-.59-.894-.59-1.533 0-.678.24-1.23.726-1.644.487-.415 1.133-.623 1.955-.623.272 0 .551.024.846.064.296.04.6.104.918.176v-.583c0-.607-.127-1.03-.375-1.277-.255-.248-.686-.367-1.3-.367-.28 0-.568.031-.863.103-.295.072-.583.16-.862.272a2.4 2.4 0 01-.28.104.488.488 0 01-.127.023c-.112 0-.168-.08-.168-.247v-.391c0-.128.016-.224.056-.28a.597.597 0 01.224-.167 4.577 4.577 0 011.005-.36 4.84 4.84 0 011.246-.151c.95 0 1.644.216 2.091.647.44.43.662 1.085.662 1.963v2.586h.016zm-3.24 1.214c.263 0 .534-.048.822-.144a1.78 1.78 0 00.758-.51 1.27 1.27 0 00.272-.512c.047-.191.08-.423.08-.694v-.335a6.66 6.66 0 00-.735-.136 6.02 6.02 0 00-.75-.048c-.535 0-.926.104-1.19.32-.263.215-.39.518-.39.917 0 .375.095.655.295.846.191.2.47.296.838.296zm6.41.862c-.144 0-.24-.024-.304-.08-.064-.048-.12-.16-.168-.311L7.586 6.726a1.398 1.398 0 01-.072-.32c0-.128.064-.2.191-.2h.783c.151 0 .255.025.31.08.065.048.113.16.16.312l1.342 5.284 1.245-5.284c.04-.16.088-.264.151-.312a.549.549 0 01.32-.08h.638c.152 0 .256.025.32.08.063.048.12.16.151.312l1.261 5.348 1.381-5.348c.048-.16.104-.264.16-.312a.52.52 0 01.311-.08h.743c.127 0 .2.065.2.2 0 .04-.009.08-.017.128a1.137 1.137 0 01-.056.2l-1.923 6.17c-.048.16-.104.263-.168.311a.51.51 0 01-.303.08h-.687c-.15 0-.255-.024-.32-.08-.063-.056-.119-.16-.15-.32L12.32 7.747l-1.23 5.14c-.04.16-.087.264-.15.32-.065.056-.177.08-.32.08l-.686.001zm10.256.215c-.415 0-.83-.048-1.229-.143-.399-.096-.71-.2-.918-.32-.128-.071-.215-.151-.247-.223a.563.563 0 01-.048-.224v-.407c0-.167.064-.247.183-.247.048 0 .096.008.144.024.048.016.12.048.2.08.271.12.566.215.878.279.32.064.63.096.95.096.502 0 .894-.088 1.165-.264a.86.86 0 00.415-.758.777.777 0 00-.215-.559c-.144-.151-.416-.287-.807-.415l-1.157-.36c-.583-.183-1.014-.454-1.277-.813a1.902 1.902 0 01-.4-1.158c0-.335.073-.63.216-.886.144-.255.335-.479.575-.654.24-.184.51-.32.83-.415.32-.096.655-.136 1.006-.136.175 0 .36.008.535.032.183.024.35.056.518.088.16.04.312.08.455.127.144.048.256.096.336.144a.69.69 0 01.24.2.43.43 0 01.071.263v.375c0 .168-.064.256-.184.256a.83.83 0 01-.303-.096 3.652 3.652 0 00-1.532-.311c-.455 0-.815.071-1.062.223-.248.152-.375.383-.375.71 0 .224.08.416.24.567.16.152.454.304.877.44l1.134.358c.574.184.99.44 1.237.767.247.327.367.702.367 1.117 0 .343-.072.655-.207.926a2.157 2.157 0 01-.583.703c-.248.2-.543.343-.886.447-.36.111-.734.167-1.142.167z"></path><path d="M.378 15.475c3.384 1.963 7.56 3.153 11.877 3.153 2.914 0 6.114-.607 9.06-1.852.44-.2.814.287.383.607-2.626 1.94-6.442 2.969-9.722 2.969-4.598 0-8.74-1.7-11.87-4.526-.247-.223-.024-.527.272-.351zm23.531-.2c.287.36-.08 2.826-1.485 4.007-.215.184-.423.088-.327-.151l.175-.439c.343-.88.802-2.198.52-2.555-.336-.43-2.22-.207-3.074-.103-.255.032-.295-.192-.063-.36 1.5-1.053 3.967-.75 4.254-.399z" fill="#F90"></path></svg>`;
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Kiro Manual Auth (Worker)</title>
<style>
:root{
--bg: 10 10 12;
--card: 18 18 22;
--muted: 160 160 175;
--text: 240 240 245;
--border: 255 255 255;
--shadow: 0 10px 30px rgba(0,0,0,.35);
--ring: 99 102 241;
--ok: 16 185 129;
--bad: 239 68 68;
--warn: 245 158 11;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
color: rgb(var(--text));
background:
radial-gradient(1200px 600px at 20% 10%, rgba(99,102,241,.22), transparent 60%),
radial-gradient(900px 520px at 80% 30%, rgba(16,185,129,.18), transparent 55%),
radial-gradient(900px 520px at 40% 90%, rgba(236,72,153,.14), transparent 55%),
linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.35) 55%, rgba(0,0,0,.75) 100%),
rgb(var(--bg));
overflow-x:hidden;
}
.container{max-width:980px;margin:0 auto;padding:40px 16px 60px}
.header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:16px}
.title{font-size:22px;font-weight:650;letter-spacing:-.02em;margin:0}
.subtitle{margin:8px 0 0;color:rgba(var(--muted),.9);font-size:13px;line-height:1.5}
.card{
border:1px solid rgba(var(--border),.10);
background: rgba(var(--card), .55);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border-radius: 18px;
box-shadow: var(--shadow);
overflow:hidden;
position:relative;
}
.card-inner{padding:18px}
.row{display:flex;gap:12px;flex-wrap:wrap;align-items:center;justify-content:space-between}
.muted{color:rgba(var(--muted),.9);font-size:12px;line-height:1.5}
.sep{height:1px;background:rgba(var(--border),.10);margin:16px 0}
.btn{
display:inline-flex;align-items:center;gap:10px;
padding:10px 14px;
border-radius:14px;
border:1px solid rgba(var(--border),.14);
background: rgba(255,255,255,.06);
color: rgb(var(--text));
cursor:pointer;
font-size:13px;font-weight:600;
transition: transform .12s ease, background .12s ease, border-color .12s ease, box-shadow .12s ease;
user-select:none;
}
.btn:hover{background: rgba(255,255,255,.09);border-color: rgba(var(--border),.20);transform: translateY(-1px)}
.btn:active{transform: translateY(0px)}
.btn:focus{outline:none;box-shadow: 0 0 0 4px rgba(var(--ring), .25)}
.btn[disabled]{opacity:.55;cursor:not-allowed;transform:none}
.btn-primary{background: rgba(255,255,255,.10);border-color: rgba(255,255,255,.16)}
.btn-ghost{background: transparent;border-color: rgba(255,255,255,.10)}
.btn-secondary{background: rgba(255,255,255,.07)}
.btn-xs{padding:7px 10px;border-radius:12px;font-size:12px}
.grid{display:grid;grid-template-columns:1fr;gap:12px}
@media (min-width: 860px){ .grid{grid-template-columns:1fr 1fr} }
.field{
border:1px solid rgba(var(--border),.10);
background: rgba(0,0,0,.22);
border-radius: 16px;
padding:12px 14px;
}
.label{font-size:11px;color:rgba(var(--muted),.9);margin-bottom:6px}
.value{font-size:13px;word-break:break-all}
.mono{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; font-size:12px}
.link-panel{
position:relative;
border:1px solid rgba(var(--border),.10);
background: rgba(0,0,0,.20);
border-radius: 18px;
padding:14px;
overflow:hidden;
transition: box-shadow .16s ease, border-color .16s ease, transform .16s ease;
}
.link-panel:hover{box-shadow: 0 12px 30px rgba(0,0,0,.35);border-color: rgba(255,255,255,.16);transform: translateY(-1px)}
.link-text{margin-top:8px;font-size:13px;opacity:.92;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Acrylic hover overlay */
.acrylic{position:absolute;inset:0;opacity:0;transition: opacity .16s ease;pointer-events:none;}
.link-panel:hover .acrylic{opacity:1}
.acrylic::before{
content:"";position:absolute;inset:0;
background: rgba(255,255,255,.10);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.acrylic::after{
content:"";position:absolute;inset:-40px;
background: radial-gradient(420px 220px at 20% 20%, rgba(255,255,255,.12), transparent 55%),
radial-gradient(420px 220px at 80% 40%, rgba(255,255,255,.08), transparent 55%);
opacity:.9;mix-blend-mode: overlay;
}
.link-actions{
position:absolute; inset:0;
display:flex;align-items:center;justify-content:center;gap:10px;
opacity:0;transition: opacity .16s ease;
pointer-events:none;
}
.link-panel:hover .link-actions{opacity:1}
.link-actions .btn{pointer-events:auto}
.status{
border:1px solid rgba(var(--border),.10);
background: rgba(0,0,0,.20);
border-radius: 16px;
padding:12px 14px;
display:flex;gap:12px;align-items:flex-start;justify-content:space-between;
}
.badge{
display:inline-flex;align-items:center;gap:8px;
font-size:11px;color:rgba(var(--muted),.95);
padding:6px 10px;border-radius:999px;
border:1px solid rgba(var(--border),.12);
background: rgba(255,255,255,.05);
white-space:nowrap;
}
.dot{width:8px;height:8px;border-radius:999px;background: rgba(255,255,255,.55);position:relative;}
.dot.ping::after{
content:"";position:absolute;inset:-6px;border-radius:999px;
border:1px solid rgba(255,255,255,.35);
animation: ping 1.2s ease-out infinite;opacity:.8;
}
@keyframes ping{0%{transform:scale(.4);opacity:.8} 100%{transform:scale(1.5);opacity:0}}
.status.ok{border-color: rgba(var(--ok), .35); background: rgba(var(--ok), .10)}
.status.bad{border-color: rgba(var(--bad), .35); background: rgba(var(--bad), .10)}
.status.warn{border-color: rgba(var(--warn), .35); background: rgba(var(--warn), .08)}
/* Modal */
.modal-backdrop{
position:fixed;inset:0;
background: rgba(0,0,0,.60);
display:none;
align-items:center;justify-content:center;
padding:18px;
z-index:50;
}
.modal-backdrop.show{display:flex}
.modal{
width:min(900px, 100%);
max-height: 88vh; /* NEW: keep modal within viewport */
border-radius: 18px;
border:1px solid rgba(var(--border),.14);
background: rgba(var(--card), .86);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: var(--shadow);
overflow:hidden;
display:flex;
flex-direction:column; /* NEW: allow internal scroll areas */
}
.modal-head{padding:16px 18px;border-bottom:1px solid rgba(var(--border),.10);flex:0 0 auto}
.modal-title{margin:0;font-size:16px;font-weight:750}
.modal-desc{margin:6px 0 0;color:rgba(var(--muted),.9);font-size:12px;line-height:1.45}
.modal-body{
padding:16px 18px;
flex: 1 1 auto; /* NEW */
min-height: 0; /* NEW: critical for flex scroll children */
display:flex; /* NEW */
flex-direction:column; /* NEW */
gap:12px; /* NEW */
}
/* NEW: scrollable content area inside modal body */
.modal-scroll{
flex: 1 1 auto;
min-height: 0;
overflow:auto;
padding-right: 4px;
border-radius: 14px;
}
/* nicer scrollbar (webkit only) */
.modal-scroll::-webkit-scrollbar{width:10px}
.modal-scroll::-webkit-scrollbar-thumb{background: rgba(255,255,255,.12); border-radius: 999px; border:2px solid rgba(0,0,0,.15)}
.modal-scroll::-webkit-scrollbar-track{background: rgba(0,0,0,.10); border-radius: 999px}
pre{
margin:0;border-radius: 14px;border:1px solid rgba(var(--border),.10);
background: rgba(0,0,0,.30);
padding:14px;
overflow:auto;
font-size:12px; line-height:1.5;
}
/* Footer pinned inside modal */
.footer-actions{
display:flex;gap:10px;flex-wrap:wrap;
padding-top: 12px;
margin-top: 0;
position: sticky; /* NEW */
bottom: 0; /* NEW */
background: linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,.20) 30%, rgba(0,0,0,.28));
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(255,255,255,.08);
}
.right{margin-left:auto}
/* Tabs */
.tabs{display:flex;gap:8px;align-items:center}
.tab{
padding:8px 10px;
border-radius: 12px;
border:1px solid rgba(var(--border),.12);
background: rgba(255,255,255,.05);
color: rgba(var(--text), .88);
font-size:12px;font-weight:650;
cursor:pointer;
transition: background .12s ease, border-color .12s ease, transform .12s ease;
user-select:none;
}
.tab:hover{background: rgba(255,255,255,.08);border-color: rgba(255,255,255,.18);transform: translateY(-1px)}
.tab.active{
background: rgba(255,255,255,.12);
border-color: rgba(255,255,255,.22);
color: rgb(var(--text));
}
/* Viewer list */
.viewer{display:flex;flex-direction:column;gap:10px;}
.kv-row{
position:relative;
border:1px solid rgba(var(--border),.10);
background: rgba(0,0,0,.22);
border-radius: 16px;
padding:12px 14px;
overflow:hidden;
}
.kv-row:hover{border-color: rgba(255,255,255,.16)}
.kv-key{font-size:11px;color:rgba(var(--muted),.92)}
.kv-val{
margin-top:6px;
font-size:12px;
word-break: break-all;
opacity:.92;
/* NEW: prevent a single token from taking the entire screen */
max-height: 140px;
overflow:auto;
padding-right: 6px;
}
.kv-val::-webkit-scrollbar{width:10px}
.kv-val::-webkit-scrollbar-thumb{background: rgba(255,255,255,.10); border-radius: 999px; border:2px solid rgba(0,0,0,.15)}
.kv-val::-webkit-scrollbar-track{background: rgba(0,0,0,.10); border-radius: 999px}
.kv-actions{
position:absolute;inset:0;
display:flex;align-items:center;justify-content:center;
opacity:0;transition: opacity .16s ease;
pointer-events:none;
}
.kv-row:hover .kv-actions{opacity:1}
.kv-actions .btn{pointer-events:auto}
.kv-acrylic{position:absolute;inset:0;opacity:0;transition: opacity .16s ease;pointer-events:none;}
.kv-row:hover .kv-acrylic{opacity:1}
.kv-acrylic::before{
content:"";position:absolute;inset:0;
background: rgba(255,255,255,.10);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
}
.kv-acrylic::after{
content:"";position:absolute;inset:-40px;
background: radial-gradient(380px 200px at 25% 30%, rgba(255,255,255,.12), transparent 58%),
radial-gradient(380px 200px at 75% 45%, rgba(255,255,255,.08), transparent 60%);
opacity:.9;mix-blend-mode: overlay;
}
.small-note{margin-top:12px;color:rgba(var(--muted),.85);font-size:12px;line-height:1.5}
.glow{
position:absolute; inset:-200px;
background: radial-gradient(600px 260px at 20% 10%, rgba(99,102,241,.20), transparent 55%),
radial-gradient(500px 240px at 80% 20%, rgba(16,185,129,.14), transparent 60%);
pointer-events:none;
opacity:.9;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div>
<h1 class="title">Kiro Manual Auth</h1>
<p class="subtitle">
Click <b>Login</b> → get CID/CS → device flow link. Hover link for <b>Copy | Open Link</b>.
We poll automatically and pop credentials when authorized.
</p>
</div>
<button id="btnLogin" class="btn btn-primary">
${AWS_SVG}
<span>Login</span>
</button>
</div>
<div class="card">
<div class="glow"></div>
<div class="card-inner">
<div class="row" style="gap:10px">
<div class="muted">
OIDC requests are proxied server-side to bypass browser CORS.
<span style="display:block;opacity:.85">Treat Refresh Token / Client Secret as secrets.</span>
</div>
<div class="row" style="justify-content:flex-end">
<button id="btnClear" class="btn btn-ghost">Clear</button>
</div>
</div>
<div class="sep"></div>
<div class="link-panel" id="linkPanel">
<div class="label">Device Flow Link</div>
<div class="link-text" id="verifyUrl" title="">—</div>
<div class="acrylic"></div>
<div class="link-actions" id="linkActions">
<button id="btnCopyLink" class="btn btn-secondary">Copy</button>
<button id="btnOpenLink" class="btn btn-secondary">Open Link</button>
</div>
</div>
<div class="sep"></div>
<div class="grid">
<div class="field">
<div class="label">User Code</div>
<div class="value" id="userCode">—</div>
</div>
<div class="field">
<div class="label">Device Code</div>
<div class="value mono" id="deviceCode">—</div>
</div>
</div>
<div class="sep"></div>
<div class="status" id="statusBox">
<div>
<div style="font-weight:700;font-size:13px">Status</div>
<div class="muted" id="statusText" style="margin-top:4px">Click Login to start.</div>
</div>
<div class="badge" id="statusBadge">
<span class="dot" id="statusDot"></span>
<span id="statusLabel">IDLE</span>
</div>
</div>
<div class="small-note">
Tip: hover the link panel to copy/open. Polling starts immediately after link generation.
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal-backdrop" id="modalBackdrop" role="dialog" aria-modal="true">
<div class="modal">
<div class="modal-head">
<div class="row" style="align-items:flex-start">
<div>
<h2 class="modal-title">Authorized ✅</h2>
<p class="modal-desc">Viewer: copy per key (CID/CS included). JSON: raw token.json.</p>
</div>
<div class="tabs" aria-label="result tabs">
<div id="tabViewer" class="tab active">Viewer</div>
<div id="tabJson" class="tab">JSON</div>
</div>
</div>
</div>
<div class="modal-body">
<!-- NEW: scroll container -->
<div class="modal-scroll" id="modalScroll">
<div id="panelViewer" class="viewer"></div>
<div id="panelJson" style="display:none">
<pre id="resultPre">{}</pre>
</div>
</div>
<div class="footer-actions">
<button id="btnCopyJson" class="btn btn-secondary">Copy JSON</button>
<button id="btnDownload" class="btn btn-secondary">Download token.json</button>
<button id="btnCloseModal" class="btn btn-ghost right">Close</button>
</div>
</div>
</div>
</div>
<script>
(() => {
const $ = (id) => document.getElementById(id);
const state = {
loading: false,
clientId: "",
clientSecret: "",
verifyUrl: "",
userCode: "",
deviceCode: "",
// polling control: no overlap; next only after last finished
pollTimer: null,
pollIntervalMs: 2000,
pollInFlight: false,
pollingEnabled: false,
lastPollOk: null, // last successful poll JSON (accessToken present)
resultJson: "", // token.json content shown in JSON tab
};
const ui = {
btnLogin: $("btnLogin"),
btnClear: $("btnClear"),
verifyUrl: $("verifyUrl"),
userCode: $("userCode"),
deviceCode: $("deviceCode"),
btnCopyLink: $("btnCopyLink"),
btnOpenLink: $("btnOpenLink"),
statusBox: $("statusBox"),
statusText: $("statusText"),
statusDot: $("statusDot"),
statusLabel: $("statusLabel"),
modalBackdrop: $("modalBackdrop"),
modalScroll: $("modalScroll"),
panelViewer: $("panelViewer"),
panelJson: $("panelJson"),
resultPre: $("resultPre"),
btnCopyJson: $("btnCopyJson"),
btnDownload: $("btnDownload"),
btnCloseModal: $("btnCloseModal"),
tabViewer: $("tabViewer"),
tabJson: $("tabJson"),
};
function escapeHtml(str) {
return String(str)
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll('"', """)
.replaceAll("'", "'");
}
function setBusy(b) {
state.loading = b;
ui.btnLogin.disabled = b;
ui.btnClear.disabled = b;
}
function setStatus(kind, text, label) {
ui.statusText.textContent = text;
ui.statusLabel.textContent = label;
ui.statusBox.classList.remove("ok", "bad", "warn");
ui.statusDot.classList.remove("ping");
ui.statusDot.style.background = "rgba(255,255,255,.55)";
if (kind === "pending" || kind === "polling") {
ui.statusBox.classList.add("warn");
ui.statusDot.classList.add("ping");
ui.statusDot.style.background = "rgba(245,158,11,.9)";
} else if (kind === "ok") {
ui.statusBox.classList.add("ok");
ui.statusDot.style.background = "rgba(16,185,129,.95)";
} else if (kind === "error") {
ui.statusBox.classList.add("bad");
ui.statusDot.style.background = "rgba(239,68,68,.95)";
}
}
function stopPolling() {
state.pollingEnabled = false;
if (state.pollTimer) clearTimeout(state.pollTimer);
state.pollTimer = null;
state.pollInFlight = false;
}
function scheduleNextPoll() {
if (!state.pollingEnabled) return;
if (state.pollTimer) clearTimeout(state.pollTimer);
state.pollTimer = setTimeout(async () => {
await pollOnce();
scheduleNextPoll();
}, state.pollIntervalMs);
}
async function apiStrict(path, body) {
const r = await fetch(path, {
method: "POST",
headers: { "content-type": "application/json" },
body: body ? JSON.stringify(body) : undefined
});
const data = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(typeof data === "string" ? data : JSON.stringify(data));
return data;
}
async function apiAllowNon200(path, body) {
const r = await fetch(path, {
method: "POST",
headers: { "content-type": "application/json" },
body: body ? JSON.stringify(body) : undefined
});
const data = await r.json().catch(() => ({}));
return { ok: r.ok, status: r.status, data };
}
function setLink(url) {
state.verifyUrl = url || "";
ui.verifyUrl.textContent = url || "—";
ui.verifyUrl.title = url || "";
ui.btnCopyLink.disabled = !url;
ui.btnOpenLink.disabled = !url;
}
function setCodes({ userCode, deviceCode }) {
state.userCode = userCode || "";
state.deviceCode = deviceCode || "";
ui.userCode.textContent = userCode || "—";
ui.deviceCode.textContent = deviceCode || "—";
}
async function copy(text) {
if (!text) return;
await navigator.clipboard.writeText(text);
}
function showModal() {
ui.modalBackdrop.classList.add("show");
// NEW: reset scroll to top when opening (optional but nice)
if (ui.modalScroll) ui.modalScroll.scrollTop = 0;
}
function hideModal() {
ui.modalBackdrop.classList.remove("show");
}
function setTab(which) {
const viewer = which === "viewer";
ui.tabViewer.classList.toggle("active", viewer);
ui.tabJson.classList.toggle("active", !viewer);
ui.panelViewer.style.display = viewer ? "flex" : "none";
ui.panelJson.style.display = viewer ? "none" : "block";
// keep scroll at top when switching
if (ui.modalScroll) ui.modalScroll.scrollTop = 0;
}
function kvRow(key, val, onCopy) {
const row = document.createElement("div");
row.className = "kv-row";
row.innerHTML = \`
<div class="kv-key">\${escapeHtml(key)}</div>
<div class="kv-val mono">\${escapeHtml(val)}</div>
<div class="kv-acrylic"></div>
<div class="kv-actions">
<button class="btn btn-secondary btn-xs">Copy</button>
</div>
\`;
const btn = row.querySelector("button");
btn.addEventListener("click", onCopy);
return row;
}
function normalizeValue(v) {
if (v === null || v === undefined) return "null";
if (typeof v === "string") return v;
return JSON.stringify(v);
}
function renderViewer({ cid, cs, pollOk }) {
ui.panelViewer.innerHTML = "";
const cidVal = cid || "";
const csVal = cs || "";
ui.panelViewer.appendChild(
kvRow("clientId", cidVal || "—", async () => {
await copy(cidVal);
setStatus("ok", "Copied: clientId", "SUCCESS");
})
);
ui.panelViewer.appendChild(
kvRow("clientSecret", csVal || "—", async () => {
await copy(csVal);
setStatus("ok", "Copied: clientSecret", "SUCCESS");
})
);
if (!pollOk || typeof pollOk !== "object") {
const msg = document.createElement("div");
msg.className = "muted";
msg.textContent = "No poll response data.";
ui.panelViewer.appendChild(msg);
return;
}
const entries = Object.entries(pollOk);
for (const [k, v] of entries) {
const val = normalizeValue(v);
ui.panelViewer.appendChild(
kvRow(k, val, async () => {
await copy(val);
setStatus("ok", "Copied: " + k, "SUCCESS");
})
);
}
}
function buildTokenJson(cid, cs, pollOk) {
const out = {
client_id: cid,
client_secret: cs,
refresh_token: pollOk && pollOk.refreshToken ? pollOk.refreshToken : null,
poll_response: pollOk || null
};
return JSON.stringify(out, null, 2);
}
async function pollOnce() {
if (!state.pollingEnabled) return;
if (state.pollInFlight) return;
state.pollInFlight = true;
try {
const { ok, status, data } = await apiAllowNon200("/api/poll", {
clientId: state.clientId,
clientSecret: state.clientSecret,
deviceCode: state.deviceCode,
});
if (data && data.accessToken) {
state.lastPollOk = data;
stopPolling();
setStatus("ok", "Authorized! Refresh token received.", "SUCCESS");
state.resultJson = buildTokenJson(state.clientId, state.clientSecret, data);
ui.resultPre.textContent = state.resultJson;
renderViewer({ cid: state.clientId, cs: state.clientSecret, pollOk: data });
setTab("viewer");
showModal();
return;
}
// PENDING (even if backend returns 400)
if (data && data.error === "authorization_pending") {
setStatus("pending", "Pending authorization... (complete it in the opened page)", "PENDING");
return;
}
if (data && data.error === "slow_down") {
state.pollIntervalMs = Math.min(state.pollIntervalMs + 2000, 10000);
setStatus(
"pending",
"Slow down requested. Polling every " + (state.pollIntervalMs / 1000).toFixed(1) + "s",
"PENDING"
);
return;
}
if (data && data.error === "expired_token") {
stopPolling();
setStatus("error", "Device code expired. Click Login again.", "EXPIRED");
return;
}
if (!ok) {
stopPolling();
setStatus("error", "Error: " + JSON.stringify(data), "ERROR");
return;
}
setStatus("pending", "Waiting... " + JSON.stringify(data), "PENDING");
} catch (e) {
stopPolling();
setStatus("error", "Polling error: " + (e && e.message ? e.message : String(e)), "ERROR");
} finally {
state.pollInFlight = false;
}
}
function startPolling() {
stopPolling();
state.pollingEnabled = true;
setStatus("pending", "Polling started... authorize in the verification page.", "PENDING");
pollOnce().finally(() => {
scheduleNextPoll();
});
}
async function loginFlow() {
stopPolling();
setBusy(true);
setLink("");
setCodes({ userCode: "", deviceCode: "" });
state.clientId = "";
state.clientSecret = "";
state.lastPollOk = null;
state.resultJson = "";
setStatus("polling", "Registering OIDC client...", "WORKING");
try {
const reg = await apiStrict("/api/register");
state.clientId = reg.clientId;
state.clientSecret = reg.clientSecret;
setStatus("polling", "Generating device flow link...", "WORKING");
const dev = await apiStrict("/api/device", {
clientId: state.clientId,
clientSecret: state.clientSecret
});
const link = dev.verificationUriComplete || dev.verificationUri || "";
setLink(link);
setCodes({ userCode: dev.userCode, deviceCode: dev.deviceCode });
state.pollIntervalMs = Math.max(2000, (dev.interval ? dev.interval * 1000 : 2000));
setStatus("pending", "Link ready. Hover to Copy/Open. Polling in background...", "PENDING");
startPolling();
} catch (e) {
stopPolling();
setStatus("error", "Error: " + (e && e.message ? e.message : String(e)), "ERROR");
} finally {
setBusy(false);
}
}
function clearAll() {
stopPolling();
state.clientId = "";
state.clientSecret = "";
state.verifyUrl = "";
state.userCode = "";
state.deviceCode = "";
state.lastPollOk = null;
state.resultJson = "";
state.pollIntervalMs = 2000;
setLink("");
setCodes({ userCode: "", deviceCode: "" });
setStatus("idle", "Cleared. Click Login to start.", "IDLE");
hideModal();
}
// ===== Bindings =====
ui.btnLogin.addEventListener("click", loginFlow);
ui.btnClear.addEventListener("click", clearAll);
ui.btnCopyLink.addEventListener("click", async () => {
if (!state.verifyUrl) return;
await copy(state.verifyUrl);
setStatus("pending", "Link copied. Continue authorization in the opened page.", "PENDING");
});
ui.btnOpenLink.addEventListener("click", () => {
if (!state.verifyUrl) return;
window.open(state.verifyUrl, "_blank", "noopener,noreferrer");
setStatus("pending", "Link opened. Complete authorization, we are polling...", "PENDING");
});
ui.btnCopyJson.addEventListener("click", async () => {
if (!state.resultJson) return;
await copy(state.resultJson);
setStatus("ok", "token.json copied.", "SUCCESS");
});
ui.btnDownload.addEventListener("click", () => {
if (!state.resultJson) return;
const blob = new Blob([state.resultJson], { type: "application/json" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "token.json";
a.click();
URL.revokeObjectURL(a.href);
setStatus("ok", "Downloaded token.json", "SUCCESS");
});
ui.btnCloseModal.addEventListener("click", hideModal);
ui.modalBackdrop.addEventListener("click", (e) => {
if (e.target === ui.modalBackdrop) hideModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") hideModal();
});
ui.tabViewer.addEventListener("click", () => setTab("viewer"));
ui.tabJson.addEventListener("click", () => setTab("json"));
// Initial state
setLink("");
setCodes({ userCode: "", deviceCode: "" });
setStatus("idle", "Click Login to start.", "IDLE");
setTab("viewer");
})();
</script>
</body>
</html>`;
}
使用方法:
Claude Code Workflow (CCW)
Claude Code Workflow (CCW) 是一个 JSON 驱动的多智能体开发框架,具有智能 CLI 编排(Gemini/Qwen/Codex)、上下文优先架构和自动化工作流执行。它将 AI 开发从简单的提示词链接转变为一个强大的编排系统。
项目地址:
catlog22/Claude-Code-Workflow
安装方式:
npm install -g claude-code-workflow
ccw install #安装工作流
ccw view #打开看板
CCW Issue Loop 工作流(需要搭配 ACE tools)
什么是 Issue Loop 工作流
Issue Loop 是 CCW (Claude Code Workflow) 中的批量问题处理工作流,专为处理项目迭代过程中积累的多个问题而设计。与单次修复不同,Issue Loop 采用 “积累 → 规划 → 队列 → 执行” 的模式,实现问题的批量发现和集中解决。
两阶段生命周期
Phase 1: 积累阶段
在项目正常迭代过程中,持续发现和记录问题:
・任务完成后 Review → /issue:discover → 自动分析代码发现潜在问题
・代码审查发现 → /issue:new → 手动创建结构化 Issue
・测试失败 → /issue:discover-by-prompt → 根据描述创建 Issue
・用户反馈 → /issue:new → 手动录入反馈问题
Phase 2: 批量解决阶段
积累足够 Issue 后,集中处理:
Step 1: /issue:plan --all-pending # 为所有待处理 Issue 生成解决方案
Step 2: /issue:queue # 形成执行队列(冲突检测 + 排序)
Step 3: /issue:execute # 批量执行(串行或并行)
Issue 状态流转:
registered → planned → queued → executing → completed命令详解
Claude 命令
• /issue:new — 根据描述注册 Issue
• /issue:discover — 多个视角自动分析代码发现问题
• /issue:discover-by-prompt — 根据问题(bug,需求)深入探索发现 Issue
• /issue:plan — 为 Issue 生成解决方案
• /issue:queue — 用于解决冲突,复用上下文,形成执行队列,可划分多个独立队列
• /issue:execute — 执行队列中的解决方案(Claude 作为协调中枢支持 agent,Codex 并行执行)
Codex 命令
• /prompt:issue-execute — 在 Codex 串行执行队列中的解决方案,支持 queue 指定,工作树隔离 (实测无中断,理论无限时长,当前合计最多跑了 1.5 天,晚上断网~~)
可视化
通过看板(ccw view 启动)可以查看 issue 状态及队列状态
使用场景
下面是个简单的使用流程:
1. 完成 功能开发
2. 执行 /issue:discover 发现技术债务
3. 执行 /issue:plan --all-pending
4. 使用 /issue:queue 形成队列
5. 使用 codex 执行 /prompt:issue-execute 批量处理
技巧
・在有充足的上下文的时候(开发途中,任务完成),使用 CLI 去提需求,生成 Issue 清单,然后再 recover 对话。
・可以将任务完成产物扔给 /issue:new 快速产出测试规划以及需求扩展。
完善中
・可视化界面队列管理,拼接,增强多人协作
下贴预告
— 全文完 (采用 CCW text-formatter skill 进行格式化) —
之前一直没有体验 codex,最近空下来了,我来一个一个 cli 工具拷打一下,接入我真实的开发任务。一看这个 Codex 读写文件的原理我 TM 头的大了。他使用指令读取文件的时候,居然不携带我的 profile.ps1。网上那篇点击率贼高的文章没个用。
我系统是
Microsoft Windows 10 专业工作站版
版本 10.0.19045 内部版本 19045
powershell 版本:
PS C:\Users\Administrator> $PSVersionTable Name Value ---- ----- PSVersion 5.1.19041.5965 PSEdition Desktop PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.19041.5965 CLRVersion 4.0. WSManStackVersion 3.0 PSRemotingProtocolVersion 2.3 SerializationVersion 1.1.0.1 原理也没什么好说的,就是 codex 调用工具的时候告诉他要用 utf8 读。
好像直接使用编辑器贴 Skill 有问题我就留下个 git 链接吧。
也不用点 Star,我很社恐…
看到大家都在折腾 Claude,我也来分享一下我自己目前在用的 Skills 清单。主打实用,涵盖前端、SEO 和 提示词生成。
1. Anthropic 官方 Skills
官方出品,必属精品。这里面我只安装了 skill-creator,用来手搓其他 Skill 非常方便。
2. UI UX Pro Max
前端神器,用来写 UI 非常好用,推荐前端佬们试试。
3. seo-review
主要用来做 AI 网站的 SEO 审查,查漏补缺。
https://github.com/leonardomso/33-js-concepts/tree/master/.opencode/skill/seo-review
4. content-creator
SEO 的好搭档。根据关键词自动创建博客文章,适合做内容营销。
5. skill-prompt-generator
主要用于生成提示词,搭配生图工具使用效果拔群,强烈推荐!
6. Planning-with-files
参考了 Manus 的 Agent 思路写的 Skill。非常适合处理多步骤的复杂任务。
最后强烈建议各位佬友,一定要多多搭建属于自己的 Skills ,从日常的一些工作流开始沉淀。
我的 Skill 迭代方法论:
手动跑通: 遇到流程,先用 Claude Code (CC) 人肉跑一遍。
总结提炼: 跑通后,让 CC 总结成一个 Skill。
评分优化: 基于第一性原理进行打分,不断迭代调优。
如果觉得手写代码麻烦,可以直接用官方的 skill-creator 进行自然语言沟通,创建一个自己的 Skill。
自己沉淀下来的 Skill,才是最好的生产力工具。
用电脑的时候,经常需要翻译一下,比如下载了个英文软件,英文又不怎么好。
十年前那会经常用一个叫做天若 ocr 的软件,遇到看不懂的,直接截图识别再翻译一下,特别方便。后来不知道怎么的就不能用了。
中间试了多个软件,都不怎么如意,或者说没有天若那个纯粹。
推荐一个叫做 pot 的软件,它很纯粹,专门用于这个应用场景 (截图翻译)。
它的问题就是识别很烂,乱七八糟得,英文中文符号大锅烩,根本看不懂。但是它可以安装插件,我安装了个 RapidOCR,就可以正常识别了。
它还有个问题就是开始开启 hdr 的话,截图界面就像是加了滤镜一般,灰蒙蒙地,灰色的字都会消失,这个我研究了半天没找到解决方案,所以干脆关闭了 hdr。
做了这番配置,使用体验就很好了,和当年得天若没啥区别了。
各位 L 站的佬友们好!
我是本坛萌新,潜水有一段时间了,一直在这个技术氛围浓厚的社区里学习。今天终于鼓起勇气发个贴,分享一个自己最近开发的练手工具,希望各位佬轻喷,也欢迎大家多提宝贵意见!
平时用夸克网盘比较多,但官方客户端的广告和臃肿大家都懂。加上我有自动化整理资源的需求,官方缺少 API 支持。
作为一名开发者,手痒之下就用 Go (Wails) + Vue3 + Element Plus 撸了这个第三方客户端。
目前项目已经打包了 Windows 版本(单文件绿色版),主打一个干净、无广、可编程,发出来分享给有需要的坛友们体验。
除了基础的文件管理(上传 / 下载 / 重命名 / 移动等),针对像我这样的 “折腾党”,做了以下增强:
虽然目前代码还在整理中暂未开源,但还是想和大家交流一下技术选型。
Wails 的方案在 Windows 下表现真的非常不错,利用系统自带的 WebView2,体积比 Electron 小很多,内存占用也低,Go 写后端逻辑也很舒服。
这是我个人最喜欢的功能,开启 API 服务后,你可以直接用 curl 或者 python 操作网盘,做一些自动化的事情:
# 1. 获取文件列表
curl "http://localhost:8080/api/files?folder_id=0" # 2. 一键转存分享链接
curl -X POST http://localhost:8080/api/save \
-H "Content-Type: application/json" \
-d '{"share_url": "[https://pan.quark.cn/s/xxx](https://pan.quark.cn/s/xxx)"}'
访问 http://localhost:8080/swagger/ 还能看到完整的接口文档。
📥 下载与安装
目前 Release 页面已上传编译好的 quarkpan.exe,下载解压即可直接运行。
GitHub 发布页: https://github.com/dpyyds/QuarkManager/releases
⚠️ 免责声明
本软件为第三方个人开发工具,仅供学习交流,严禁用于商业用途。
使用第三方客户端可能存在风险,请大家自行评估,后果自负。
如涉及侵权请联系删除。
初来乍到,希望能融入 L 站这个大家庭。如果觉得这个小工具好用,求各位佬去 GitHub 点个 Star 🌟 支持一下,也欢迎在评论区交流 bug 和建议!最近已经用 OpenCode 搭配 Oh-My-OpenCode 替换 CC 了
在源码中找到 100 个小技巧
git status )