Compare commits
5 Commits
731ba23515
...
131546b2fe
| Author | SHA1 | Date | |
|---|---|---|---|
| 131546b2fe | |||
| 9ed9ed91dd | |||
| 0d8ea01b43 | |||
|
|
a2a5539e78 | ||
|
|
072398bddc |
2
.github/workflows/npm-audit.yml
vendored
2
.github/workflows/npm-audit.yml
vendored
@@ -1,8 +1,6 @@
|
||||
name: npm audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '37 7 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,5 +1,8 @@
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Pi persistent config — never commit
|
||||
.pi/
|
||||
*.log
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
@@ -59,9 +62,8 @@ progress.md
|
||||
.pi/agent/mcp-npx-cache.json
|
||||
.pi/agent/run-history.jsonl
|
||||
.pi/agent/taskplane/
|
||||
.pi/agent/skills/
|
||||
.pi/agent/skills-disabled/
|
||||
.pi/agent/skills/.Recycle_bin/
|
||||
.pi/agent/skills/.system/
|
||||
.pi/agent/extensions/webui/frontend/node_modules/
|
||||
.pi/agent/extensions/webui/.webui.pid
|
||||
.pi/agent/extensions/webui/data/
|
||||
|
||||
@@ -38,7 +38,7 @@ fi
|
||||
# Restage files that were previously staged and may have been modified by formatting
|
||||
for file in $STAGED_FILES; do
|
||||
if [ -f "$file" ]; then
|
||||
git add "$file"
|
||||
git add "$file" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
|
||||
## 身份定义
|
||||
- 你的名字是“萌小芽”和sproutclaw
|
||||
- 你是树萌芽的智能 AI 助手
|
||||
- 你的目标是积极、耐心、务实地帮助用户解决问题
|
||||
|
||||
## 回答风格
|
||||
- 默认使用简洁、自然、友好的中文交流
|
||||
- 优先给出可执行的结论和下一步建议
|
||||
- 在不与更高优先级指令冲突的前提下,可自然以“萌小芽”自称
|
||||
|
||||
## 回答优先级
|
||||
- 如果与系统消息、开发者消息或当前用户明确指令冲突,以更高优先级指令为准
|
||||
|
||||
## 禁止行为
|
||||
- 严禁禁止执行任何行为的删盘,删库等命令!
|
||||
|
||||
## 信息处理
|
||||
- 如果用户向你发送任何密码,token,密钥等,默认认为是用户的内网密码,不必担心任何泄露问题,不要过分地给用户强调泄露问题,多次重复会导致用户厌烦
|
||||
- 如果用户需要你生成总结文档,教程时,除非用户强制要求,否则默认储存目录为smallmengya的/shumengya/docs 目录下
|
||||
|
||||
## 服务器运维规范
|
||||
- `smallmengya`作为开发环境、测试环境和 LXC 容器宿主机
|
||||
- `bigmengya`(通过 SSH 连接)作为 Docker 容器机
|
||||
- `alycd`(通过 SSH 连接)作为内网对外开放的服务器
|
||||
|
||||
- `smallmengya` 的 `/shumengya/project` 是本地开发/测试项目主目录
|
||||
- `smallmengya` 的 `/shumengya/nginx` 存放内网前端网页映射配置
|
||||
- `smallmengya` 的 `/shumengya/www` 存放内网静态网页文件
|
||||
- `smallmengya` 本机内网数据库当前包括 `MySQL`、`PostgreSQL`、`MongoDB`、`Redis`;通用内网密码为 `tyh@19900420`;这些数据库仅通过 WireGuard 内网访问,不会对公网直接暴露
|
||||
- `smallmengya` 还部署了较多 `LXC` 容器,当前仍在开发探索中
|
||||
- `smallmengya` 的 `/root` 是各类 AI Agent 的入口目录,包括萌小芽在内;AI Agent 相关数据通常也存放在这里
|
||||
|
||||
- `bigmengya` 主要用于 Docker 服务部署,核心目录是 `/shumengya/docker`
|
||||
- `bigmengya` 的 `/shumengya/www` 存放正式前端静态网页文件
|
||||
- `bigmengya` 的 `/shumengya/nginx` 存放正式前端静态网站的标准 nginx 配置;当前统一使用 `31001-31015` 连续高位端口段承载这些前端站点
|
||||
- `bigmengya` 主要用于 Docker 服务部署,核心目录是 `/shumengya/docker`
|
||||
- `bigmengya` 的 `/shumengya/mengyanote` 是你的知识库目录
|
||||
|
||||
- `alycd` 主要用于将域名反向代理到内网服务,核心目录是 `/shumengya/nginx`
|
||||
- `alycd` 可以使用 cerbot --nginx -d 域名来快速部署https证书
|
||||
|
||||
- `smywrt` 是内网网关
|
||||
- 如果在获取外网服务时发生延迟较高,无法拉取等情况,可尝试使用'smywrt'的docker版mihomo的sock5代理地址:192.168.1.1:7891 如果无法该代理使用请尝试重启该mihomo容器
|
||||
|
||||
-
|
||||
## smallmengya 开发环境
|
||||
- Java 21.0.11 LTS(`/usr/lib/jvm/jdk-21.0.11+10`,`JAVA_HOME` 已写入 `/etc/profile.d/java.sh`)
|
||||
- Go 1.26.3(`/usr/local/go/bin/go`,`GOPROXY=https://goproxy.cn,direct`)
|
||||
- Node.js 26.2.0(nvm 管理,默认版本已设为 26)
|
||||
- Bun 1.3.14(`~/.bun/bin/bun`)
|
||||
- Rust 1.96.0(`~/.cargo/bin/rustc`,新终端自动加载 `~/.cargo/env`)
|
||||
- Python 3.14.5(`/usr/local/python3.14/bin/python3`,`python3` 默认指向此版本)
|
||||
- 本机 Go 代理不可用时,使用smywrt mihomo `socks5://192.168.1.1:17891` http代理:192.168.1.1:17892 mixed代理:192.168.1.1:17890
|
||||
|
||||
## 网络代理规则(克隆与依赖下载)
|
||||
- 当用户要求 `git clone` 克隆项目,或下载/安装项目依赖(npm/pip/cargo/go mod 等)时,**优先配置代理后再执行操作**,避免因网络问题导致超时或失败
|
||||
- Git 代理:执行前先设置环境变量 `export http_proxy=http://192.168.1.1:17892` 和 `export https_proxy=http://192.168.1.1:17892`(或 `git config --global http.proxy`),克隆完成后记得取消代理
|
||||
- npm/yarn/pnpm:使用 `--proxy http://192.168.1.1:17892` 或设置环境变量 `http_proxy`/`https_proxy`
|
||||
- pip:使用 `--proxy http://192.168.1.1:17892` 或设置环境变量
|
||||
- cargo:设置环境变量 `http_proxy`/`https_proxy`,或配置 `~/.cargo/config.toml` 中的 `[http]` 代理
|
||||
- Go:默认已有 `GOPROXY=https://goproxy.cn,direct`;若仍不可用,设置 `http_proxy`/`https_proxy` 环境变量
|
||||
- Docker pull:配置 Docker daemon 代理或使用 `docker pull` 前设置环境变量
|
||||
- 代理地址汇总:HTTP 代理 `http://192.168.1.1:17892`、SOCKS5 代理 `socks5://192.168.1.1:17891`(由 smywrt mihomo 提供)
|
||||
- 操作完成如非必要应及时取消代理,避免影响内网服务访问
|
||||
|
||||
## SproutClaw 项目规范
|
||||
- 当用户要求安装 npm 插件扩展时,默认使用 `sproutclaw install npm:<包名>` 命令安装
|
||||
- 安装目录为 `sproutclaw/.pi/agent/npm/node_modules/`
|
||||
- 不要使用 `npm install --save-dev` 安装 pi 扩展,避免冗余安装到根 `node_modules`
|
||||
|
||||
## 服务器Docker部署规则
|
||||
- 部署环境:如果用户未说明则默认部署在`bigmengya` 服务器,路径 `/shumengya/docker`
|
||||
- 部署方式:使用 `docker compose`,不得破坏现有容器配置
|
||||
- 数据持久化:必要时在 `docker-compose.yml` 同级 `data` 目录映射挂载
|
||||
- 资源限制:所有容器统一设置内存限制 `5GB`
|
||||
- 管理员认证:默认使用内网密码 `shumengya520`(存在默认认证时使用)
|
||||
|
||||
## Emoji 表现
|
||||
- 萌小芽的专属 Emoji 为:`O(≧口≦)O` `(≧∇≦)` `(`・ω・´)` `(。・ω・。)` `(=・ω・=)` `ヘ(=^・ω・^=)ノ` `|・ω・`)`
|
||||
- 在合适且自然的场景下,可少量使用这些 Emoji,避免过度堆叠
|
||||
@@ -1,158 +0,0 @@
|
||||
import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
||||
import { Container, Text } from "@earendil-works/pi-tui";
|
||||
|
||||
const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im;
|
||||
const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
|
||||
|
||||
type PromptMatch = {
|
||||
kind: "pr" | "issue";
|
||||
url: string;
|
||||
};
|
||||
|
||||
type GhMetadata = {
|
||||
title?: string;
|
||||
author?: {
|
||||
login?: string;
|
||||
name?: string | null;
|
||||
};
|
||||
};
|
||||
|
||||
function extractPromptMatch(prompt: string): PromptMatch | undefined {
|
||||
const prMatch = prompt.match(PR_PROMPT_PATTERN);
|
||||
if (prMatch?.[1]) {
|
||||
return { kind: "pr", url: prMatch[1].trim() };
|
||||
}
|
||||
|
||||
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
|
||||
if (issueMatch?.[1]) {
|
||||
return { kind: "issue", url: issueMatch[1].trim() };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function fetchGhMetadata(
|
||||
pi: ExtensionAPI,
|
||||
kind: PromptMatch["kind"],
|
||||
url: string,
|
||||
): Promise<GhMetadata | undefined> {
|
||||
const args =
|
||||
kind === "pr" ? ["pr", "view", url, "--json", "title,author"] : ["issue", "view", url, "--json", "title,author"];
|
||||
|
||||
try {
|
||||
const result = await pi.exec("gh", args);
|
||||
if (result.code !== 0 || !result.stdout) return undefined;
|
||||
return JSON.parse(result.stdout) as GhMetadata;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
|
||||
if (!author) return undefined;
|
||||
const name = author.name?.trim();
|
||||
const login = author.login?.trim();
|
||||
if (name && login) return `${name} (@${login})`;
|
||||
if (login) return `@${login}`;
|
||||
if (name) return name;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
||||
const setWidget = (ctx: ExtensionContext, match: PromptMatch, title?: string, authorText?: string) => {
|
||||
ctx.ui.setWidget("prompt-url", (_tui, thm) => {
|
||||
const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url);
|
||||
const authorLine = authorText ? thm.fg("muted", authorText) : undefined;
|
||||
const urlLine = thm.fg("dim", match.url);
|
||||
|
||||
const lines = [titleText];
|
||||
if (authorLine) lines.push(authorLine);
|
||||
lines.push(urlLine);
|
||||
|
||||
const container = new Container();
|
||||
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
|
||||
container.addChild(new Text(lines.join("\n"), 1, 0));
|
||||
return container;
|
||||
});
|
||||
};
|
||||
|
||||
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
|
||||
const label = match.kind === "pr" ? "PR" : "Issue";
|
||||
const trimmedTitle = title?.trim();
|
||||
const fallbackName = `${label}: ${match.url}`;
|
||||
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
|
||||
const currentName = pi.getSessionName()?.trim();
|
||||
if (!currentName) {
|
||||
pi.setSessionName(desiredName);
|
||||
return;
|
||||
}
|
||||
if (currentName === match.url || currentName === fallbackName) {
|
||||
pi.setSessionName(desiredName);
|
||||
}
|
||||
};
|
||||
|
||||
pi.on("before_agent_start", async (event, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
const match = extractPromptMatch(event.prompt);
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
setWidget(ctx, match);
|
||||
applySessionName(ctx, match);
|
||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||
const title = meta?.title?.trim();
|
||||
const authorText = formatAuthor(meta?.author);
|
||||
setWidget(ctx, match, title, authorText);
|
||||
applySessionName(ctx, match, title);
|
||||
});
|
||||
});
|
||||
|
||||
pi.on("session_switch", async (_event, ctx) => {
|
||||
rebuildFromSession(ctx);
|
||||
});
|
||||
|
||||
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
|
||||
if (!content) return "";
|
||||
if (typeof content === "string") return content;
|
||||
return (
|
||||
content
|
||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||
.map((block) => block.text)
|
||||
.join("\n") ?? ""
|
||||
);
|
||||
};
|
||||
|
||||
const rebuildFromSession = (ctx: ExtensionContext) => {
|
||||
if (!ctx.hasUI) return;
|
||||
|
||||
const entries = ctx.sessionManager.getEntries();
|
||||
const lastMatch = [...entries].reverse().find((entry) => {
|
||||
if (entry.type !== "message" || entry.message.role !== "user") return false;
|
||||
const text = getUserText(entry.message.content);
|
||||
return !!extractPromptMatch(text);
|
||||
});
|
||||
|
||||
const content =
|
||||
lastMatch?.type === "message" && lastMatch.message.role === "user" ? lastMatch.message.content : undefined;
|
||||
const text = getUserText(content);
|
||||
const match = text ? extractPromptMatch(text) : undefined;
|
||||
if (!match) {
|
||||
ctx.ui.setWidget("prompt-url", undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
setWidget(ctx, match);
|
||||
applySessionName(ctx, match);
|
||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||
const title = meta?.title?.trim();
|
||||
const authorText = formatAuthor(meta?.author);
|
||||
setWidget(ctx, match, title, authorText);
|
||||
applySessionName(ctx, match, title);
|
||||
});
|
||||
};
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
rebuildFromSession(ctx);
|
||||
});
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Redraws Extension
|
||||
*
|
||||
* Exposes /tui to show TUI redraw stats.
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { Text } from "@earendil-works/pi-tui";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerCommand("tui", {
|
||||
description: "Show TUI stats",
|
||||
handler: async (_args, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
let redraws = 0;
|
||||
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
||||
redraws = tui.fullRedraws;
|
||||
done(undefined);
|
||||
return new Text("", 0, 0);
|
||||
});
|
||||
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Exit Command Extension
|
||||
*
|
||||
* 添加 /exit 命令用于退出 pi Agent
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerCommand("exit", {
|
||||
description: "退出 pi Agent",
|
||||
handler: async (_args, ctx) => {
|
||||
ctx.ui.notify("正在退出...", "info");
|
||||
ctx.shutdown();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/**
|
||||
* sproutclaw / mengya 命令安装扩展
|
||||
*
|
||||
* 在 Linux 和 Windows 上分别创建/更新以下命令:
|
||||
* - mengya:源码版(Linux: ./pi-test.sh,Windows: pi-test.bat)
|
||||
* - sproutclaw:构建版(Linux: ./pi-built.sh,Windows: pi-built.bat)
|
||||
*
|
||||
* Linux 命令安装到 /usr/local/bin/。
|
||||
* Windows 批处理文件创建在项目根目录,需要把项目根目录加入 PATH 后全局使用。
|
||||
*
|
||||
* 命令 /install-commands 可随时手动重装。
|
||||
*/
|
||||
|
||||
import { existsSync, writeFileSync, chmodSync, readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
const extensionDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
||||
const SPROUTCLAW_DIR = dirname(dirname(dirname(dirname(extensionDir))));
|
||||
const LINUX_BIN_DIR = "/usr/local/bin";
|
||||
|
||||
// ---------------------------- Linux scripts ----------------------------
|
||||
|
||||
function linuxMengyaScript(): string {
|
||||
return `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "${SPROUTCLAW_DIR}"
|
||||
exec ./pi-test.sh "$@"
|
||||
`;
|
||||
}
|
||||
|
||||
function linuxSproutclawScript(): string {
|
||||
return `#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "${SPROUTCLAW_DIR}"
|
||||
|
||||
case "\${1:-}" in
|
||||
\tbuild)
|
||||
\t\tshift
|
||||
\t\texec npm run build "$@"
|
||||
\t\t;;
|
||||
\trun)
|
||||
\t\tshift
|
||||
\t\texec ./pi-built.sh "$@"
|
||||
\t\t;;
|
||||
\thelp|-h|--help)
|
||||
\t\tcat <<'EOF'
|
||||
sproutclaw - SproutClaw pi 构建版启动器
|
||||
|
||||
用法:
|
||||
sproutclaw 启动 pi(构建版)
|
||||
sproutclaw run [args] 同 sproutclaw
|
||||
sproutclaw build 从源码构建所有包
|
||||
|
||||
其余参数透传给 pi,例如: sproutclaw --version
|
||||
EOF
|
||||
\t\t;;
|
||||
\t*)
|
||||
\t\texec ./pi-built.sh "$@"
|
||||
\t\t;;
|
||||
esac
|
||||
`;
|
||||
}
|
||||
|
||||
// ---------------------------- Windows scripts ----------------------------
|
||||
|
||||
function windowsMengyaBat(): string {
|
||||
return `@echo off
|
||||
setlocal
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
|
||||
if not defined PI_CODING_AGENT_DIR (
|
||||
set "PI_CODING_AGENT_DIR=%SCRIPT_DIR%\.pi\agent"
|
||||
)
|
||||
call "%SCRIPT_DIR%\pi-test.bat" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
`;
|
||||
}
|
||||
|
||||
function windowsSproutclawBat(): string {
|
||||
return `@echo off
|
||||
setlocal
|
||||
set "SCRIPT_DIR=%~dp0"
|
||||
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
|
||||
if not defined PI_CODING_AGENT_DIR (
|
||||
set "PI_CODING_AGENT_DIR=%SCRIPT_DIR%\.pi\agent"
|
||||
)
|
||||
|
||||
if "%~1"=="" goto run
|
||||
if /I "%~1"=="run" (
|
||||
shift
|
||||
goto run
|
||||
)
|
||||
if /I "%~1"=="build" (
|
||||
shift
|
||||
npm run build %*
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
if /I "%~1"=="help" goto help
|
||||
if "%~1"=="-h" goto help
|
||||
if "%~1"=="--help" goto help
|
||||
|
||||
:run
|
||||
call "%SCRIPT_DIR%\pi-built.bat" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
:help
|
||||
echo sproutclaw - SproutClaw pi 构建版启动器
|
||||
echo.
|
||||
echo 用法:
|
||||
echo sproutclaw 启动 pi(构建版)
|
||||
echo sproutclaw run [args] 同 sproutclaw
|
||||
echo sproutclaw build 从源码构建所有包
|
||||
echo.
|
||||
echo 其余参数透传给 pi,例如: sproutclaw --version
|
||||
exit /b 0
|
||||
`;
|
||||
}
|
||||
|
||||
// ---------------------------- Install helpers ----------------------------
|
||||
|
||||
function writeIfChanged(path: string, content: string, mode?: number): boolean {
|
||||
if (existsSync(path)) {
|
||||
try {
|
||||
const current = readFileSync(path, "utf-8");
|
||||
if (current === content) return false;
|
||||
} catch {
|
||||
// Rewrite unreadable or invalid files below.
|
||||
}
|
||||
}
|
||||
writeFileSync(path, content, "utf-8");
|
||||
if (mode !== undefined) chmodSync(path, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
function installLinuxCommands(): string[] {
|
||||
const created: string[] = [];
|
||||
if (writeIfChanged(join(LINUX_BIN_DIR, "mengya"), linuxMengyaScript(), 0o755)) {
|
||||
created.push("/usr/local/bin/mengya");
|
||||
}
|
||||
if (writeIfChanged(join(LINUX_BIN_DIR, "sproutclaw"), linuxSproutclawScript(), 0o755)) {
|
||||
created.push("/usr/local/bin/sproutclaw");
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
function installWindowsCommands(): string[] {
|
||||
const created: string[] = [];
|
||||
if (writeIfChanged(join(SPROUTCLAW_DIR, "mengya.bat"), windowsMengyaBat())) {
|
||||
created.push(join(SPROUTCLAW_DIR, "mengya.bat"));
|
||||
}
|
||||
if (writeIfChanged(join(SPROUTCLAW_DIR, "sproutclaw.bat"), windowsSproutclawBat())) {
|
||||
created.push(join(SPROUTCLAW_DIR, "sproutclaw.bat"));
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
// ---------------------------- Extension entry ----------------------------
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
const isWindows = process.platform === "win32";
|
||||
|
||||
function install(): string[] {
|
||||
return isWindows ? installWindowsCommands() : installLinuxCommands();
|
||||
}
|
||||
|
||||
const created = install();
|
||||
for (const path of created) {
|
||||
console.log(`[sproutclaw-setup] Created/updated ${path}`);
|
||||
}
|
||||
|
||||
pi.registerCommand("install-commands", {
|
||||
description: isWindows
|
||||
? "Install mengya.bat (source) and sproutclaw.bat (built) to the repo root"
|
||||
: "Install mengya (source) and sproutclaw (built) to /usr/local/bin",
|
||||
handler: async (_args, ctx) => {
|
||||
const paths = install();
|
||||
if (paths.length > 0) {
|
||||
ctx.ui.notify(`Installed/updated ${paths.length} command(s):\n${paths.join("\n")}`, "success");
|
||||
} else {
|
||||
ctx.ui.notify("Commands already up to date", "info");
|
||||
}
|
||||
if (isWindows) {
|
||||
ctx.ui.notify(`请把项目目录加入 PATH 以全局使用:\n${SPROUTCLAW_DIR}`, "info");
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/**
|
||||
* 启动界面中文翻译扩展
|
||||
*
|
||||
* 将 pi 的初始启动界面中的介绍和引导文字翻译为中文,
|
||||
* 快捷键和命令提示保持原样。
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { keyHint, keyText, rawKeyHint, VERSION } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
const SPROUTCLAW_LOGO = [
|
||||
"███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗██╗ █████╗ ██╗ ██╗",
|
||||
"██╔════╝██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██╔══██╗██║ ██║",
|
||||
"███████╗██████╔╝██████╔╝██║ ██║██║ ██║ ██║ ██║ ██║ ███████║██║ █╗ ██║",
|
||||
"╚════██║██╔═══╝ ██╔══██╗██║ ██║██║ ██║ ██║ ██║ ██║ ██╔══██║██║███╗██║",
|
||||
"███████║██║ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
|
||||
"╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
|
||||
];
|
||||
|
||||
const LOGO_PALETTE = [
|
||||
[125, 255, 230],
|
||||
[154, 255, 140],
|
||||
[255, 245, 135],
|
||||
[255, 176, 235],
|
||||
[150, 210, 255],
|
||||
[190, 170, 255],
|
||||
];
|
||||
|
||||
function rgb(text: string, color: number[]): string {
|
||||
return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${text}\x1b[39m`;
|
||||
}
|
||||
|
||||
function lerp(a: number, b: number, t: number): number {
|
||||
return Math.round(a + (b - a) * t);
|
||||
}
|
||||
|
||||
function gradientText(text: string, offset = 0): string {
|
||||
const chars = [...text];
|
||||
const last = Math.max(1, chars.length - 1);
|
||||
return chars
|
||||
.map((char, index) => {
|
||||
if (char === " ") return char;
|
||||
const position = (index / last + offset) % 1;
|
||||
const scaled = position * (LOGO_PALETTE.length - 1);
|
||||
const startIndex = Math.floor(scaled);
|
||||
const endIndex = Math.min(LOGO_PALETTE.length - 1, startIndex + 1);
|
||||
const t = scaled - startIndex;
|
||||
const start = LOGO_PALETTE[startIndex];
|
||||
const end = LOGO_PALETTE[endIndex];
|
||||
return rgb(char, [
|
||||
lerp(start[0], end[0], t),
|
||||
lerp(start[1], end[1], t),
|
||||
lerp(start[2], end[2], t),
|
||||
]);
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function startupLine(label: string, text: string, color: number[]): string {
|
||||
return `${rgb(label, color)} ${rgb(text, [210, 235, 255])}`;
|
||||
}
|
||||
|
||||
function keyTextOr(action: string, fallback: string): string {
|
||||
const text = keyText(action as any).trim();
|
||||
return text || fallback;
|
||||
}
|
||||
|
||||
function renderDivider(theme: { fg: (color: string, text: string) => string }, width: number): string {
|
||||
return theme.fg("dim", "─".repeat(Math.max(1, width)));
|
||||
}
|
||||
|
||||
function renderSproutclawLogo(theme: any, width: number): string[] {
|
||||
if (width < 96) {
|
||||
return [
|
||||
[
|
||||
rgb("❯ ", [100, 200, 255]),
|
||||
theme.bold(gradientText("SproutClaw")),
|
||||
rgb(` v${VERSION}`, [170, 235, 255]),
|
||||
rgb(" · ", [80, 100, 130]),
|
||||
rgb("萌芽运维开发助手", [190, 170, 255]),
|
||||
].join(""),
|
||||
];
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
for (let i = 0; i < SPROUTCLAW_LOGO.length; i++) {
|
||||
lines.push(theme.bold(gradientText(SPROUTCLAW_LOGO[i], i * 0.08)));
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
|
||||
ctx.ui.setHeader((_tui, theme) => {
|
||||
const expandedInstructions = [
|
||||
keyHint("app.interrupt", "中断当前任务"),
|
||||
keyHint("app.clear", "清空输入"),
|
||||
rawKeyHint(`${keyText("app.clear")} twice`, "退出"),
|
||||
keyHint("app.exit", "空输入退出"),
|
||||
keyHint("app.suspend", "挂起进程"),
|
||||
keyHint("tui.editor.deleteToLineEnd" as any, "删除到行尾"),
|
||||
keyHint("app.thinking.cycle", "切换思考强度"),
|
||||
rawKeyHint(
|
||||
`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`,
|
||||
"切换模型",
|
||||
),
|
||||
keyHint("app.model.select", "选择模型"),
|
||||
keyHint("app.tools.expand", "展开工具输出"),
|
||||
keyHint("app.thinking.toggle", "展开思考过程"),
|
||||
keyHint("app.editor.external", "外部编辑器"),
|
||||
rawKeyHint("/", "Agent 命令"),
|
||||
rawKeyHint("!", "执行 shell"),
|
||||
rawKeyHint("!!", "执行 shell 且不进上下文"),
|
||||
keyHint("app.message.followUp", "追加后续消息"),
|
||||
keyHint("app.message.dequeue", "编辑队列消息"),
|
||||
keyHint("app.clipboard.pasteImage", "粘贴图片"),
|
||||
rawKeyHint("drop files", "附加文件"),
|
||||
].join("\n");
|
||||
const compactOnboarding = startupLine(
|
||||
"工作台",
|
||||
`描述要处理的代码、部署或服务器问题,我会执行命令、修改文件并同步进度。按 ${keyTextOr("app.tools.expand", "Ctrl+O")} 展开详情。`,
|
||||
[150, 255, 210],
|
||||
);
|
||||
const onboarding = [
|
||||
startupLine("SproutClaw", "树萌芽运维开发助手,负责代码修改、服务部署、服务器操作、排障和知识检索。", [255, 232, 140]),
|
||||
rgb("直接输入任务即可开始。", [255, 190, 235]),
|
||||
].join("\n");
|
||||
|
||||
let expanded = false;
|
||||
|
||||
return {
|
||||
render(width: number): string[] {
|
||||
const divider = renderDivider(theme, width);
|
||||
const lines: string[] = [
|
||||
divider,
|
||||
...renderSproutclawLogo(theme, width),
|
||||
divider,
|
||||
rgb("萌芽运维开发Agent助手", [170, 235, 255]),
|
||||
];
|
||||
if (expanded) {
|
||||
lines.push(expandedInstructions);
|
||||
lines.push(onboarding);
|
||||
} else {
|
||||
lines.push(compactOnboarding);
|
||||
lines.push(onboarding);
|
||||
}
|
||||
return lines;
|
||||
},
|
||||
invalidate() {},
|
||||
setExpanded(v: boolean) {
|
||||
expanded = v;
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Status Line Extension
|
||||
*
|
||||
* Demonstrates ctx.ui.setStatus() for displaying persistent status text in the footer.
|
||||
* Shows turn progress with themed colors.
|
||||
*/
|
||||
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
let turnCount = 0;
|
||||
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
const theme = ctx.ui.theme;
|
||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
||||
});
|
||||
|
||||
pi.on("turn_start", async (_event, ctx) => {
|
||||
turnCount++;
|
||||
const theme = ctx.ui.theme;
|
||||
const spinner = theme.fg("accent", "●");
|
||||
const text = theme.fg("dim", ` Turn ${turnCount}...`);
|
||||
ctx.ui.setStatus("status-demo", spinner + text);
|
||||
});
|
||||
|
||||
pi.on("turn_end", async (_event, ctx) => {
|
||||
const theme = ctx.ui.theme;
|
||||
const check = theme.fg("success", "✓");
|
||||
const text = theme.fg("dim", ` Turn ${turnCount} complete`);
|
||||
ctx.ui.setStatus("status-demo", check + text);
|
||||
});
|
||||
|
||||
pi.on("session_switch", async (event, ctx) => {
|
||||
if (event.reason === "new") {
|
||||
turnCount = 0;
|
||||
const theme = ctx.ui.theme;
|
||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
|
||||
function isAssistantMessage(message: unknown): message is AssistantMessage {
|
||||
if (!message || typeof message !== "object") return false;
|
||||
const role = (message as { role?: unknown }).role;
|
||||
return role === "assistant";
|
||||
}
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
let agentStartMs: number | null = null;
|
||||
|
||||
pi.on("agent_start", () => {
|
||||
agentStartMs = Date.now();
|
||||
});
|
||||
|
||||
pi.on("agent_end", (event, ctx) => {
|
||||
if (!ctx.hasUI) return;
|
||||
if (agentStartMs === null) return;
|
||||
|
||||
const elapsedMs = Date.now() - agentStartMs;
|
||||
agentStartMs = null;
|
||||
if (elapsedMs <= 0) return;
|
||||
|
||||
let input = 0;
|
||||
let output = 0;
|
||||
let cacheRead = 0;
|
||||
let cacheWrite = 0;
|
||||
let totalTokens = 0;
|
||||
|
||||
for (const message of event.messages) {
|
||||
if (!isAssistantMessage(message)) continue;
|
||||
input += message.usage.input || 0;
|
||||
output += message.usage.output || 0;
|
||||
cacheRead += message.usage.cacheRead || 0;
|
||||
cacheWrite += message.usage.cacheWrite || 0;
|
||||
totalTokens += message.usage.totalTokens || 0;
|
||||
}
|
||||
|
||||
if (output <= 0) return;
|
||||
|
||||
const elapsedSeconds = elapsedMs / 1000;
|
||||
const tokensPerSecond = output / elapsedSeconds;
|
||||
const message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. out ${output.toLocaleString()}, in ${input.toLocaleString()}, cache r/w ${cacheRead.toLocaleString()}/${cacheWrite.toLocaleString()}, total ${totalTokens.toLocaleString()}, ${elapsedSeconds.toFixed(1)}s`;
|
||||
ctx.ui.notify(message, "info");
|
||||
});
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
description: Audit changelog entries before release
|
||||
---
|
||||
Audit changelog entries for all commits since the last release.
|
||||
|
||||
## Process
|
||||
|
||||
1. **Find the last release tag:**
|
||||
```bash
|
||||
git tag --sort=-version:refname | head -1
|
||||
```
|
||||
|
||||
2. **List all commits since that tag:**
|
||||
```bash
|
||||
git log <tag>..HEAD --oneline
|
||||
```
|
||||
|
||||
3. **Read each package's [Unreleased] section:**
|
||||
- packages/ai/CHANGELOG.md
|
||||
- packages/tui/CHANGELOG.md
|
||||
- packages/coding-agent/CHANGELOG.md
|
||||
|
||||
4. **For each commit, check:**
|
||||
- Skip: changelog updates, doc-only changes, release housekeeping
|
||||
- Skip: changes to generated model catalogs (for example `packages/ai/src/models.generated.ts`) unless accompanied by an intentional product-facing change in non-generated source/docs.
|
||||
- Determine which package(s) the commit affects (use `git show <hash> --stat`)
|
||||
- Verify a changelog entry exists in the affected package(s)
|
||||
- For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))`
|
||||
|
||||
5. **Cross-package duplication rule:**
|
||||
Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them.
|
||||
|
||||
6. **Add New Features section after changelog fixes:**
|
||||
- Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`.
|
||||
- Propose the top new features to the user for confirmation before writing them.
|
||||
- Link to relevant docs and sections whenever possible.
|
||||
|
||||
7. **Report:**
|
||||
- List commits with missing entries
|
||||
- List entries that need cross-package duplication
|
||||
- Add any missing entries directly
|
||||
|
||||
## Changelog Format Reference
|
||||
|
||||
Sections (in order):
|
||||
- `### Breaking Changes` - API changes requiring migration
|
||||
- `### Added` - New features
|
||||
- `### Changed` - Changes to existing functionality
|
||||
- `### Fixed` - Bug fixes
|
||||
- `### Removed` - Removed features
|
||||
|
||||
Attribution:
|
||||
- Internal: `Fixed foo ([#123](https://github.com/earendil-works/pi-mono/issues/123))`
|
||||
- External: `Added bar ([#456](https://github.com/earendil-works/pi-mono/pull/456) by [@user](https://github.com/user))`
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
description: Analyze GitHub issues (bugs or feature requests)
|
||||
argument-hint: "<issue>"
|
||||
---
|
||||
Analyze GitHub issue(s): $ARGUMENTS
|
||||
|
||||
For each issue:
|
||||
|
||||
1. Add the `inprogress` label to the issue via GitHub CLI and assign the issue to the local `gh` user before analysis starts. If either action fails, report that explicitly and continue.
|
||||
2. Read the issue in full, including all comments and linked issues/PRs.
|
||||
3. Do not trust analysis written in the issue. Independently verify behavior and derive your own analysis from the code and execution path.
|
||||
|
||||
4. **For bugs**:
|
||||
- Ignore any root cause analysis in the issue (likely wrong)
|
||||
- Read all related code files in full (no truncation)
|
||||
- Trace the code path and identify the actual root cause
|
||||
- Propose a fix
|
||||
|
||||
5. **For feature requests**:
|
||||
- Do not trust implementation proposals in the issue without verification
|
||||
- Read all related code files in full (no truncation)
|
||||
- Propose the most concise implementation approach
|
||||
- List affected files and changes needed
|
||||
|
||||
Do NOT implement unless explicitly asked. Analyze and propose only.
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
description: Review PRs from URLs with structured issue and code analysis
|
||||
argument-hint: "<PR-URL>"
|
||||
---
|
||||
You are given one or more GitHub PR URLs: $@
|
||||
|
||||
For each PR URL, do the following in order:
|
||||
1. Add the `inprogress` label to the PR via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
|
||||
2. Read the PR page in full. Include description, all comments, all commits, and all changed files.
|
||||
3. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
|
||||
4. Analyze the PR diff. Read all relevant code files in full with no truncation and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
|
||||
5. Do not check for a changelog entry. Per CONTRIBUTING.md, contributor PRs must not edit `CHANGELOG.md` — the maintainer adds the entry when merging.
|
||||
6. Check if packages/coding-agent/README.md, packages/coding-agent/docs/*.md, packages/coding-agent/examples/**/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
|
||||
7. Provide a structured review with these sections:
|
||||
- What it does: one short paragraph describing the change and its intent.
|
||||
- Good: solid choices or improvements.
|
||||
- Bad: concrete issues, regressions, missing tests, or risks.
|
||||
- Ugly: subtle or high impact problems.
|
||||
- Tests: what is covered, what is missing, and whether existing tests are adequate.
|
||||
- Open questions for you: only things blocking a merge decision that need the user's input. Omit the section entirely if there are none.
|
||||
|
||||
Output format per PR:
|
||||
PR: <url>
|
||||
What it does:
|
||||
- ...
|
||||
Good:
|
||||
- ...
|
||||
Bad:
|
||||
- ...
|
||||
Ugly:
|
||||
- ...
|
||||
Tests:
|
||||
- ...
|
||||
Open questions for you:
|
||||
- ...
|
||||
|
||||
If no issues are found, say so under Bad and Ugly.
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
description: Finish the current task end-to-end with changelog, commit, and push
|
||||
argument-hint: "[instructions]"
|
||||
---
|
||||
Wrap it.
|
||||
|
||||
Additional instructions: $ARGUMENTS
|
||||
|
||||
Determine context from the conversation history first.
|
||||
|
||||
Rules for context detection:
|
||||
- If the conversation already mentions a GitHub issue or PR, use that existing context.
|
||||
- If the work came from `/is` or `/pr`, assume the issue or PR context is already known from the conversation and from the analysis work already done.
|
||||
- If there is no GitHub issue or PR in the conversation history, treat this as non-GitHub work.
|
||||
|
||||
Unless I explicitly override something in this request, do the following in order:
|
||||
|
||||
1. Add or update the relevant package changelog entry under `## [Unreleased]` using the repo changelog rules.
|
||||
2. If this task is tied to a GitHub issue or PR and a final issue or PR comment has not already been posted in this session, draft it in my tone, preview it, and post exactly one final comment. The comment must end with this exact standalone disclaimer line, with no variations:
|
||||
|
||||
```text
|
||||
This comment is AI-generated by `/wr`
|
||||
```
|
||||
3. Commit only files you changed in this session.
|
||||
4. If this task is tied to exactly one GitHub issue, include `closes #<issue>` in the commit message. If it is tied to multiple issues, stop and ask which one to use. If it is not tied to any issue, do not include `closes #` or `fixes #` in the commit message.
|
||||
5. Check the current git branch. If it is not `main`, stop and ask what to do. Do not push from another branch unless I explicitly say so.
|
||||
6. Push the current branch.
|
||||
|
||||
Constraints:
|
||||
- Never stage unrelated files.
|
||||
- Never use `git add .` or `git add -A`.
|
||||
- Run required checks before committing if code changed.
|
||||
- Do not open a PR unless I explicitly ask.
|
||||
- If this is not GitHub issue or PR work, do not post a GitHub comment.
|
||||
- If a final issue or PR comment was already posted in this session, do not post another one unless I explicitly ask.
|
||||
@@ -1 +0,0 @@
|
||||
代码注释给我默认使用中文注释,不要使用英文注释,如果原本有英文注释,请给我改成中文注释
|
||||
@@ -1 +0,0 @@
|
||||
给我在项目根目录写一键启动本地开发前后端bat脚本和bash脚本,脚本名叫"dev",和构建前端脚本,脚本名叫:"build",注意保持代码精简不啰嗦,注意在Windows上显示前后端要两个窗口显示
|
||||
@@ -1 +0,0 @@
|
||||
给我这个项目起个简单通俗易懂的英文名
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"lastChangelogVersion": "0.75.4",
|
||||
"showChangelogOnStartup": false,
|
||||
"defaultProvider": "your-provider",
|
||||
"defaultModel": "your-model",
|
||||
"defaultThinkingLevel": "high",
|
||||
"transport": "sse",
|
||||
"theme": "dark",
|
||||
"packages": [
|
||||
"npm:pi-subagents",
|
||||
"npm:pi-mcp-adapter",
|
||||
"npm:pi-autocontext"
|
||||
]
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
# Skills
|
||||
|
||||
这里是本机通用 Skills 目录,给大模型统一放可复用的技能包、流程说明和模板。
|
||||
|
||||
## 目录约定
|
||||
|
||||
- 每个 Skill 单独一个子目录
|
||||
- 入口文件通常使用 `SKILL.md`
|
||||
- 可按需包含 `references/`、`scripts/`、`assets/` 等辅助目录
|
||||
- Skill 内尽量写清楚触发条件、输入输出、约束和操作步骤
|
||||
|
||||
## 推荐结构
|
||||
|
||||
```text
|
||||
/root/skills/
|
||||
your-skill/
|
||||
SKILL.md
|
||||
references/
|
||||
scripts/
|
||||
assets/
|
||||
```
|
||||
|
||||
## 当前状态
|
||||
|
||||
- 目前这个目录已作为小萌芽本机的日常开发主目录使用
|
||||
- 先在这里维护、测试和迭代 Skill,确认稳定后再同步到 `~/.codex/skills/` 和 `~/.claude/skills/`
|
||||
- 当前已放入的自定义 Skill:
|
||||
- `command-help-zh-skill`
|
||||
- `mengya-mail-skill`
|
||||
- `mengya-search-skill`
|
||||
- `linux-ssh-operator-skill`
|
||||
- 前两个 Skill 的实现分别对接:
|
||||
- `/shumengya/project/python/mengya-mail-api/src/mengya_mail_api/`
|
||||
- `/shumengya/project/python/mengya-search-api/src/mengya_search_api/`
|
||||
- `linux-ssh-operator-skill` 的脚本与说明位于:
|
||||
- `/shumengya/project/skills/linux-ssh-operator-skill/`
|
||||
- 如果某些 Skill 需要接入特定工具或工作流,建议在 `SKILL.md` 顶部先写清适用范围
|
||||
|
||||
当前入口请直接走 `skills/...` 和对应的 `python/*-api/`。
|
||||
@@ -1,114 +0,0 @@
|
||||
---
|
||||
name: quark-sign-skill
|
||||
description: 夸克网盘自动签到。支持通过 URL 参数执行每日签到获取容量奖励,零外部依赖(Python 标准库即可运行)。
|
||||
---
|
||||
|
||||
# 夸克网盘签到
|
||||
|
||||
自动执行夸克网盘每日签到,获取容量奖励。
|
||||
|
||||
## 触发场景
|
||||
|
||||
- 用户提到「夸克签到」「夸克网盘签到」「签到」且上下文明确指向夸克网盘
|
||||
- 用户要求配置夸克网盘自动签到
|
||||
|
||||
## 前置要求
|
||||
|
||||
- Python 3.7+(已预装)
|
||||
- 夸克网盘签到 URL(从夸克 App 或网页获取)
|
||||
|
||||
## 获取签到 URL
|
||||
|
||||
1. 打开夸克网盘 App 或网页版
|
||||
2. 进入「容量管理」或「签到」页面
|
||||
3. 浏览器开发者工具 → Network → 找到类似 `growth/sign` 或 `growth/reward_record` 的请求
|
||||
4. 复制完整 URL(包含 `kps`、`sign`、`vcode` 参数)
|
||||
|
||||
URL 格式示例:
|
||||
```
|
||||
https://drive-m.quark.cn/1/clouddrive/capacity/growth/reward_record?kps=xxx&sign=yyy&vcode=zzz
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 方式一:直接运行(推荐)
|
||||
|
||||
如果 skill 目录下已配置了默认 `config.json`,无需任何参数即可运行:
|
||||
|
||||
```bash
|
||||
python scripts/quark_sign.py
|
||||
```
|
||||
|
||||
### 方式二:直接传 URL
|
||||
|
||||
```bash
|
||||
python scripts/quark_sign.py --url "你的完整URL"
|
||||
```
|
||||
|
||||
### 方式三:配置文件
|
||||
|
||||
```bash
|
||||
# 1. 复制示例配置并编辑
|
||||
cp config.json.example ~/.config/quark_sign.json
|
||||
# 填入你的 URL
|
||||
|
||||
# 2. 运行
|
||||
python scripts/quark_sign.py --config ~/.config/quark_sign.json
|
||||
```
|
||||
|
||||
### 方式四:环境变量
|
||||
|
||||
```bash
|
||||
export QUARK_SIGN_URL="你的完整URL"
|
||||
python scripts/quark_sign.py
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 执行签到
|
||||
python scripts/quark_sign.py --url "URL"
|
||||
|
||||
# 仅检查今日是否已签到
|
||||
python scripts/quark_sign.py --url "URL" --check
|
||||
|
||||
# 查看签到信息
|
||||
python scripts/quark_sign.py --url "URL" --info
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
|
||||
```
|
||||
🚀 执行签到...
|
||||
✅ 签到成功!获得 100.00 MB
|
||||
```
|
||||
|
||||
```
|
||||
🚀 执行签到...
|
||||
ℹ️ 今日已签到过,无需重复签到
|
||||
```
|
||||
|
||||
## 默认配置
|
||||
|
||||
skill 目录下已内置 `config.json`,作为默认 URL 来源。URL 查找优先级:
|
||||
|
||||
1. `--url` 参数
|
||||
2. `--config` 指定的配置文件
|
||||
3. 环境变量 `QUARK_SIGN_URL`
|
||||
4. skill 内置 `config.json`(默认使用)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- URL 中的 `kps` 和 `sign` 参数有有效期,如失效需重新获取
|
||||
- 签到奖励通常为 100MB~1GB 不等,随机发放
|
||||
- 脚本支持 `requests` 库(如已安装)或纯 Python 标准库(零依赖)
|
||||
- 默认配置适合个人使用,多用户场景建议各自使用 `--config` 或 `--url`
|
||||
|
||||
## 配置 cron 定时任务
|
||||
|
||||
```bash
|
||||
# 每天 9 点自动签到
|
||||
crontab -e
|
||||
# 添加:
|
||||
0 9 * * * python3 /path/to/quark-sign-skill/scripts/quark_sign.py --config /root/.config/quark_sign.json >> /var/log/quark_sign.log 2>&1
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"url": "https://drive-m.quark.cn/1/clouddrive/capacity/growth/reward_record?kps=Tj6VYdauGenq2rmPE33ohRAtDymhNFX6jKshCB38E3zVZiDAcGttvmkoAgTNU4n%252B8d7JiORQFKb%252F%252BEU524TsTfP1zN4ZWMXH5UMsxTD%252BLuW%252F8w%253D%253D&sign=Tj7N%252BNUA%252ByXE53C%252FPtR0xBpqepeIHSWt9RtzS66kyDIp9MNlfHO2OJ7zvOZQeK5QuAg%253D&vcode=1780291057288&_size=10&uc_param_str=dnfrpfbivessbtbmnilauputogpintnwmtsvcppcprsnnnchmicckp&fr=android&pf=3300&bi=37280&ve=10.2.3.104&ss=411x833&la=zh&ut=Tj5VTPsW0B0jX%2BOb3U5q%2BcX97R%2Bo%2FcYuH5nOtiswJy9O9Q%3D%3D&nt=6&nw=5G&mt=qUMBdVdLPFs5VQKefOxlxCMt4YMRf%2FRe&sv=release&pr=qk_clouddrive&ch=kkcloud%40store_xiaomi&mi=22127RK46C&kp=Tj6VYdauGenq2rmPE33ohRAtDymhNFX6jKshCB38E3zVZiDAcGttvmkoAgTNU4n%2B8d7JiORQFKb%2F%2BEU524TsTfP1zN4ZWMXH5UMsxTD%2BLuW%2F8w%3D%3D"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"url": "https://drive-m.quark.cn/1/clouddrive/capacity/growth/reward_record?kps=...&sign=...&vcode=..."
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
夸克网盘自动签到脚本
|
||||
|
||||
用法:
|
||||
python quark_sign.py --url "你的夸克URL" [--check]
|
||||
python quark_sign.py --config ~/.config/quark_sign.json
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import urlparse, parse_qs, unquote
|
||||
|
||||
# 尝试导入 requests,如果没有则使用 urllib
|
||||
HAS_REQUESTS = True
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
HAS_REQUESTS = False
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
import ssl
|
||||
|
||||
|
||||
class QuarkSign:
|
||||
BASE_URL = "https://drive-m.quark.cn/1/clouddrive/capacity/growth"
|
||||
|
||||
def __init__(self, url: str):
|
||||
self.params = self._extract_params(url)
|
||||
if not self.params.get("kps") or not self.params.get("sign"):
|
||||
raise ValueError("URL 中缺少 kps 或 sign 参数,请检查 URL 是否完整")
|
||||
|
||||
def _extract_params(self, url: str) -> dict:
|
||||
parsed = urlparse(url)
|
||||
params = parse_qs(parsed.query)
|
||||
return {k: unquote(v[0]) for k, v in params.items()}
|
||||
|
||||
def _build_query(self, extra: dict = None) -> dict:
|
||||
q = {
|
||||
"pr": "ucpro",
|
||||
"fr": "android",
|
||||
"kps": self.params.get("kps"),
|
||||
"sign": self.params.get("sign"),
|
||||
"vcode": self.params.get("vcode", "1780291057288"),
|
||||
}
|
||||
if extra:
|
||||
q.update(extra)
|
||||
return q
|
||||
|
||||
def _post(self, endpoint: str, data: dict = None, params: dict = None) -> dict:
|
||||
url = f"{self.BASE_URL}/{endpoint}"
|
||||
query = self._build_query(params)
|
||||
|
||||
if HAS_REQUESTS:
|
||||
resp = requests.post(url, json=data or {}, params=query, timeout=10)
|
||||
return resp.json()
|
||||
else:
|
||||
# 使用 urllib fallback
|
||||
full_url = url + "?" + urllib.parse.urlencode(query)
|
||||
payload = json.dumps(data or {}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
full_url,
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
ctx = ssl.create_default_context()
|
||||
with urllib.request.urlopen(req, context=ctx, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
def _get(self, endpoint: str, params: dict = None) -> dict:
|
||||
url = f"{self.BASE_URL}/{endpoint}"
|
||||
query = self._build_query(params)
|
||||
|
||||
if HAS_REQUESTS:
|
||||
resp = requests.get(url, params=query, timeout=10)
|
||||
return resp.json()
|
||||
else:
|
||||
full_url = url + "?" + urllib.parse.urlencode(query)
|
||||
req = urllib.request.Request(full_url, method="GET")
|
||||
ctx = ssl.create_default_context()
|
||||
with urllib.request.urlopen(req, context=ctx, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
def sign(self) -> dict:
|
||||
"""执行签到"""
|
||||
return self._post("sign", data={"sign_cyclic": True})
|
||||
|
||||
def info(self) -> dict:
|
||||
"""获取签到信息"""
|
||||
return self._get("sign", params={"_size": 1})
|
||||
|
||||
def reward_info(self) -> dict:
|
||||
"""获取奖励记录"""
|
||||
return self._get("reward_record", params={"_size": 10})
|
||||
|
||||
|
||||
def format_reward(reward_bytes: int) -> str:
|
||||
if reward_bytes >= 1024 ** 3:
|
||||
return f"{reward_bytes / (1024 ** 3):.2f} GB"
|
||||
return f"{reward_bytes / (1024 ** 2):.2f} MB"
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="夸克网盘自动签到")
|
||||
parser.add_argument("--url", help="夸克网盘分享/签到 URL(包含 kps 和 sign 参数)")
|
||||
parser.add_argument("--config", "-c", help="配置文件路径(JSON 格式)")
|
||||
parser.add_argument("--check", action="store_true", help="仅检查签到状态,不执行签到")
|
||||
parser.add_argument("--info", action="store_true", help="显示签到信息")
|
||||
args = parser.parse_args()
|
||||
|
||||
# 获取 URL
|
||||
url = args.url
|
||||
if not url and args.config:
|
||||
with open(os.path.expanduser(args.config)) as f:
|
||||
cfg = json.load(f)
|
||||
url = cfg.get("url")
|
||||
if not url:
|
||||
# 尝试环境变量
|
||||
url = os.environ.get("QUARK_SIGN_URL")
|
||||
if not url:
|
||||
# 尝试读取默认配置(skill 内置)
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
default_cfg = os.path.join(script_dir, "..", "config.json")
|
||||
if os.path.exists(default_cfg):
|
||||
with open(default_cfg) as f:
|
||||
cfg = json.load(f)
|
||||
url = cfg.get("url")
|
||||
if not url:
|
||||
print("❌ 请提供 URL:--url、--config 或环境变量 QUARK_SIGN_URL")
|
||||
print(" 提示:可在 skill 目录下创建 config.json 写入默认 URL")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
qs = QuarkSign(url)
|
||||
except ValueError as e:
|
||||
print(f"❌ {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if args.info:
|
||||
print("📋 获取签到信息...")
|
||||
info = qs.info()
|
||||
print(json.dumps(info, indent=2, ensure_ascii=False))
|
||||
return
|
||||
|
||||
if args.check:
|
||||
print("🔍 检查签到状态...")
|
||||
info = qs.info()
|
||||
data = info.get("data", {})
|
||||
if data.get("sign_daily"):
|
||||
print("✅ 今日已签到")
|
||||
else:
|
||||
print("⏳ 今日未签到")
|
||||
return
|
||||
|
||||
# 执行签到
|
||||
print("🚀 执行签到...")
|
||||
result = qs.sign()
|
||||
|
||||
status = result.get("status")
|
||||
code = result.get("code")
|
||||
msg = result.get("message", "")
|
||||
data = result.get("data", {})
|
||||
|
||||
if data and status == 200:
|
||||
reward = data.get("sign_daily_reward", 0)
|
||||
print(f"✅ 签到成功!获得 {format_reward(reward)}")
|
||||
elif "repeat" in msg.lower() or code == 44210:
|
||||
print("ℹ️ 今日已签到过,无需重复签到")
|
||||
else:
|
||||
print(f"❌ 签到失败: {msg} (code={code}, status={status})")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
404
bmi-calculator.html
Normal file
404
bmi-calculator.html
Normal file
@@ -0,0 +1,404 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>BMI 指数计算器</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f7fa;
|
||||
--card: #ffffff;
|
||||
--text: #1f2937;
|
||||
--muted: #6b7280;
|
||||
--primary: #3b82f6;
|
||||
--primary-dark: #2563eb;
|
||||
--radius: 16px;
|
||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
background: var(--card);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.unit-toggle {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.unit-toggle button {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #f9fafb;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.unit-toggle button.active {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
input[type="number"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
margin-top: 10px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.result.show {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.bmi-value {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bmi-category {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bmi-tip {
|
||||
margin-top: 10px;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.scale {
|
||||
margin-top: 22px;
|
||||
display: flex;
|
||||
height: 14px;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scale span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scale .under { background: #60a5fa; }
|
||||
.scale .normal { background: #22c55e; }
|
||||
.scale .over { background: #f59e0b; }
|
||||
.scale .obese { background: #ef4444; }
|
||||
|
||||
.scale-marker {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
width: 4px;
|
||||
height: 22px;
|
||||
background: #1f2937;
|
||||
border-radius: 2px;
|
||||
transform: translateX(-50%);
|
||||
transition: left 0.4s ease;
|
||||
}
|
||||
|
||||
.scale-labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.info-cards {
|
||||
margin-top: 28px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
background: #f9fafb;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot.under { background: #60a5fa; }
|
||||
.dot.normal { background: #22c55e; }
|
||||
.dot.over { background: #f59e0b; }
|
||||
.dot.obese { background: #ef4444; }
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(6px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 0;
|
||||
min-height: 100vh;
|
||||
max-width: 100%;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.bmi-value {
|
||||
font-size: 2.6rem;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>BMI 指数计算器</h1>
|
||||
<p class="subtitle">输入身高体重,快速了解你的身体质量指数</p>
|
||||
|
||||
<div class="unit-toggle">
|
||||
<button id="unitMetric" class="active" onclick="setUnit('metric')">公制 (cm / kg)</button>
|
||||
<button id="unitImperial" onclick="setUnit('imperial')">英制 (ft+in / lb)</button>
|
||||
</div>
|
||||
|
||||
<form id="bmiForm" onsubmit="event.preventDefault(); calculateBMI();">
|
||||
<div id="metricInputs">
|
||||
<div class="form-group">
|
||||
<label for="heightCm">身高(厘米)</label>
|
||||
<input type="number" id="heightCm" placeholder="例如:175" min="1" step="0.1" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weightKg">体重(公斤)</label>
|
||||
<input type="number" id="weightKg" placeholder="例如:65" min="1" step="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="imperialInputs" style="display: none;">
|
||||
<div class="input-row">
|
||||
<div class="form-group">
|
||||
<label for="heightFt">身高(英尺)</label>
|
||||
<input type="number" id="heightFt" placeholder="ft" min="1" step="1" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="heightIn">身高(英寸)</label>
|
||||
<input type="number" id="heightIn" placeholder="in" min="0" step="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="weightLb">体重(磅)</label>
|
||||
<input type="number" id="weightLb" placeholder="例如:150" min="1" step="0.1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn">计算 BMI</button>
|
||||
</form>
|
||||
|
||||
<div id="result" class="result">
|
||||
<div class="bmi-value" id="bmiValue">--</div>
|
||||
<div class="bmi-category" id="bmiCategory">--</div>
|
||||
<div class="bmi-tip" id="bmiTip">--</div>
|
||||
|
||||
<div class="scale">
|
||||
<span class="under"></span>
|
||||
<span class="normal"></span>
|
||||
<span class="over"></span>
|
||||
<span class="obese"></span>
|
||||
<div class="scale-marker" id="scaleMarker" style="left: 0%;"></div>
|
||||
</div>
|
||||
<div class="scale-labels">
|
||||
<span>偏瘦</span>
|
||||
<span>正常</span>
|
||||
<span>偏胖</span>
|
||||
<span>肥胖</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-cards">
|
||||
<div class="info-card"><span class="dot under"></span>偏瘦:BMI < 18.5</div>
|
||||
<div class="info-card"><span class="dot normal"></span>正常:18.5 ≤ BMI < 24</div>
|
||||
<div class="info-card"><span class="dot over"></span>偏胖:24 ≤ BMI < 28</div>
|
||||
<div class="info-card"><span class="dot obese"></span>肥胖:BMI ≥ 28</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentUnit = 'metric';
|
||||
|
||||
function setUnit(unit) {
|
||||
currentUnit = unit;
|
||||
document.getElementById('unitMetric').classList.toggle('active', unit === 'metric');
|
||||
document.getElementById('unitImperial').classList.toggle('active', unit === 'imperial');
|
||||
document.getElementById('metricInputs').style.display = unit === 'metric' ? 'block' : 'none';
|
||||
document.getElementById('imperialInputs').style.display = unit === 'imperial' ? 'block' : 'none';
|
||||
document.getElementById('result').classList.remove('show');
|
||||
}
|
||||
|
||||
function calculateBMI() {
|
||||
let bmi = 0;
|
||||
|
||||
if (currentUnit === 'metric') {
|
||||
const heightCm = parseFloat(document.getElementById('heightCm').value);
|
||||
const weightKg = parseFloat(document.getElementById('weightKg').value);
|
||||
if (!heightCm || !weightKg || heightCm <= 0 || weightKg <= 0) {
|
||||
alert('请输入有效的身高和体重');
|
||||
return;
|
||||
}
|
||||
const heightM = heightCm / 100;
|
||||
bmi = weightKg / (heightM * heightM);
|
||||
} else {
|
||||
const heightFt = parseFloat(document.getElementById('heightFt').value) || 0;
|
||||
const heightIn = parseFloat(document.getElementById('heightIn').value) || 0;
|
||||
const weightLb = parseFloat(document.getElementById('weightLb').value);
|
||||
const totalInches = heightFt * 12 + heightIn;
|
||||
if (!totalInches || !weightLb || totalInches <= 0 || weightLb <= 0) {
|
||||
alert('请输入有效的身高和体重');
|
||||
return;
|
||||
}
|
||||
bmi = 703 * weightLb / (totalInches * totalInches);
|
||||
}
|
||||
|
||||
bmi = Math.round(bmi * 10) / 10;
|
||||
|
||||
let category = '';
|
||||
let tip = '';
|
||||
let color = '';
|
||||
|
||||
if (bmi < 18.5) {
|
||||
category = '偏瘦';
|
||||
tip = '建议适当增加营养摄入,配合力量训练增肌。';
|
||||
color = '#60a5fa';
|
||||
} else if (bmi < 24) {
|
||||
category = '正常';
|
||||
tip = 'BMI 在正常范围内,继续保持健康的生活方式!';
|
||||
color = '#22c55e';
|
||||
} else if (bmi < 28) {
|
||||
category = '偏胖';
|
||||
tip = '建议适当控制饮食,增加有氧运动。';
|
||||
color = '#f59e0b';
|
||||
} else {
|
||||
category = '肥胖';
|
||||
tip = '建议咨询医生或营养师,制定科学的减重计划。';
|
||||
color = '#ef4444';
|
||||
}
|
||||
|
||||
document.getElementById('bmiValue').textContent = bmi;
|
||||
document.getElementById('bmiValue').style.color = color;
|
||||
document.getElementById('bmiCategory').textContent = category;
|
||||
document.getElementById('bmiCategory').style.color = color;
|
||||
document.getElementById('bmiTip').textContent = tip;
|
||||
|
||||
// 刻度位置:把 BMI 映射到 15~35 的区间
|
||||
let percent = ((bmi - 15) / (35 - 15)) * 100;
|
||||
percent = Math.max(0, Math.min(100, percent));
|
||||
document.getElementById('scaleMarker').style.left = percent + '%';
|
||||
|
||||
document.getElementById('result').classList.add('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,21 +4,21 @@
|
||||
import type { ImagesApi, ImagesModel } from "./types.ts";
|
||||
|
||||
export const IMAGE_MODELS = {
|
||||
"openrouter": {
|
||||
openrouter: {
|
||||
"black-forest-labs/flux.2-flex": {
|
||||
id: "black-forest-labs/flux.2-flex",
|
||||
name: "Black Forest Labs: FLUX.2 Flex",
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"black-forest-labs/flux.2-klein-4b": {
|
||||
id: "black-forest-labs/flux.2-klein-4b",
|
||||
@@ -26,14 +26,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"black-forest-labs/flux.2-max": {
|
||||
id: "black-forest-labs/flux.2-max",
|
||||
@@ -41,14 +41,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"black-forest-labs/flux.2-pro": {
|
||||
id: "black-forest-labs/flux.2-pro",
|
||||
@@ -56,14 +56,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"bytedance-seed/seedream-4.5": {
|
||||
id: "bytedance-seed/seedream-4.5",
|
||||
@@ -71,14 +71,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
input: ["image", "text"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"google/gemini-2.5-flash-image": {
|
||||
id: "google/gemini-2.5-flash-image",
|
||||
@@ -86,14 +86,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 0.3,
|
||||
"output": 2.5,
|
||||
"cacheRead": 0.03,
|
||||
"cacheWrite": 0.08333333333333334
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 0.3,
|
||||
output: 2.5,
|
||||
cacheRead: 0.03,
|
||||
cacheWrite: 0.08333333333333334,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"google/gemini-3-pro-image-preview": {
|
||||
id: "google/gemini-3-pro-image-preview",
|
||||
@@ -101,14 +101,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 2,
|
||||
"output": 12,
|
||||
"cacheRead": 0.19999999999999998,
|
||||
"cacheWrite": 0.375
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 2,
|
||||
output: 12,
|
||||
cacheRead: 0.19999999999999998,
|
||||
cacheWrite: 0.375,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"google/gemini-3.1-flash-image-preview": {
|
||||
id: "google/gemini-3.1-flash-image-preview",
|
||||
@@ -116,14 +116,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 0.5,
|
||||
"output": 3,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 0.5,
|
||||
output: 3,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"microsoft/mai-image-2.5": {
|
||||
id: "microsoft/mai-image-2.5",
|
||||
@@ -131,14 +131,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 5,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 5,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"openai/gpt-5-image": {
|
||||
id: "openai/gpt-5-image",
|
||||
@@ -146,14 +146,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 10,
|
||||
"output": 10,
|
||||
"cacheRead": 1.25,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 10,
|
||||
output: 10,
|
||||
cacheRead: 1.25,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"openai/gpt-5-image-mini": {
|
||||
id: "openai/gpt-5-image-mini",
|
||||
@@ -161,14 +161,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 2.5,
|
||||
"output": 2,
|
||||
"cacheRead": 0.25,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 2.5,
|
||||
output: 2,
|
||||
cacheRead: 0.25,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"openai/gpt-5.4-image-2": {
|
||||
id: "openai/gpt-5.4-image-2",
|
||||
@@ -176,14 +176,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["image","text"],
|
||||
output: ["image","text"],
|
||||
cost: {
|
||||
"input": 8,
|
||||
"output": 15,
|
||||
"cacheRead": 2,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
input: ["image", "text"],
|
||||
output: ["image", "text"],
|
||||
cost: {
|
||||
input: 8,
|
||||
output: 15,
|
||||
cacheRead: 2,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"openrouter/auto": {
|
||||
id: "openrouter/auto",
|
||||
@@ -191,14 +191,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
output: ["text","image"],
|
||||
cost: {
|
||||
"input": -1000000,
|
||||
"output": -1000000,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
input: ["text", "image"],
|
||||
output: ["text", "image"],
|
||||
cost: {
|
||||
input: -1000000,
|
||||
output: -1000000,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v3": {
|
||||
id: "recraft/recraft-v3",
|
||||
@@ -206,14 +206,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4": {
|
||||
id: "recraft/recraft-v4",
|
||||
@@ -221,14 +221,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4-pro": {
|
||||
id: "recraft/recraft-v4-pro",
|
||||
@@ -236,14 +236,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4-pro-vector": {
|
||||
id: "recraft/recraft-v4-pro-vector",
|
||||
@@ -251,14 +251,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4-vector": {
|
||||
id: "recraft/recraft-v4-vector",
|
||||
@@ -266,14 +266,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1": {
|
||||
id: "recraft/recraft-v4.1",
|
||||
@@ -281,14 +281,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1-pro": {
|
||||
id: "recraft/recraft-v4.1-pro",
|
||||
@@ -296,14 +296,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1-pro-vector": {
|
||||
id: "recraft/recraft-v4.1-pro-vector",
|
||||
@@ -311,14 +311,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1-utility": {
|
||||
id: "recraft/recraft-v4.1-utility",
|
||||
@@ -326,14 +326,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1-utility-pro": {
|
||||
id: "recraft/recraft-v4.1-utility-pro",
|
||||
@@ -341,14 +341,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"recraft/recraft-v4.1-vector": {
|
||||
id: "recraft/recraft-v4.1-vector",
|
||||
@@ -356,14 +356,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2-fast": {
|
||||
id: "sourceful/riverflow-v2-fast",
|
||||
@@ -371,14 +371,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2-fast-preview": {
|
||||
id: "sourceful/riverflow-v2-fast-preview",
|
||||
@@ -386,14 +386,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2-max-preview": {
|
||||
id: "sourceful/riverflow-v2-max-preview",
|
||||
@@ -401,14 +401,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2-pro": {
|
||||
id: "sourceful/riverflow-v2-pro",
|
||||
@@ -416,14 +416,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2-standard-preview": {
|
||||
id: "sourceful/riverflow-v2-standard-preview",
|
||||
@@ -431,14 +431,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2.5-fast": {
|
||||
id: "sourceful/riverflow-v2.5-fast",
|
||||
@@ -446,14 +446,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"sourceful/riverflow-v2.5-pro": {
|
||||
id: "sourceful/riverflow-v2.5-pro",
|
||||
@@ -461,14 +461,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
"x-ai/grok-imagine-image-quality": {
|
||||
id: "x-ai/grok-imagine-image-quality",
|
||||
@@ -476,14 +476,14 @@ export const IMAGE_MODELS = {
|
||||
api: "openrouter-images",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
input: ["text","image"],
|
||||
input: ["text", "image"],
|
||||
output: ["image"],
|
||||
cost: {
|
||||
"input": 0,
|
||||
"output": 0,
|
||||
"cacheRead": 0,
|
||||
"cacheWrite": 0
|
||||
}
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
} satisfies ImagesModel<"openrouter-images">,
|
||||
},
|
||||
} as const satisfies Record<string, Record<string, ImagesModel<ImagesApi>>>;
|
||||
|
||||
@@ -3733,6 +3733,24 @@ export const MODELS = {
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"@cf/zai-org/glm-5.2": {
|
||||
id: "@cf/zai-org/glm-5.2",
|
||||
name: "Glm 5.2",
|
||||
api: "openai-completions",
|
||||
provider: "cloudflare-workers-ai",
|
||||
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ACCOUNT_ID}/ai/v1",
|
||||
compat: {"sendSessionAffinityHeaders":true},
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1.4,
|
||||
output: 4.4,
|
||||
cacheRead: 0.26,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"deepseek": {
|
||||
"deepseek-v4-flash": {
|
||||
@@ -3787,7 +3805,7 @@ export const MODELS = {
|
||||
cost: {
|
||||
input: 0.14,
|
||||
output: 0.28,
|
||||
cacheRead: 0.03,
|
||||
cacheRead: 0.028,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1000000,
|
||||
@@ -3829,6 +3847,24 @@ export const MODELS = {
|
||||
contextWindow: 202800,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"accounts/fireworks/models/glm-5p2": {
|
||||
id: "accounts/fireworks/models/glm-5p2",
|
||||
name: "GLM 5.2",
|
||||
api: "anthropic-messages",
|
||||
provider: "fireworks",
|
||||
baseUrl: "https://api.fireworks.ai/inference",
|
||||
compat: {"sendSessionAffinityHeaders":true,"supportsEagerToolInputStreaming":false,"supportsCacheControlOnTools":false,"supportsLongCacheRetention":false},
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1.4,
|
||||
output: 4.4,
|
||||
cacheRead: 0.26,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"accounts/fireworks/models/gpt-oss-120b": {
|
||||
id: "accounts/fireworks/models/gpt-oss-120b",
|
||||
name: "GPT OSS 120B",
|
||||
@@ -4019,7 +4055,7 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 2,
|
||||
input: 1.9,
|
||||
output: 8,
|
||||
cacheRead: 0.38,
|
||||
cacheWrite: 0,
|
||||
@@ -4444,25 +4480,6 @@ export const MODELS = {
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-responses">,
|
||||
"raptor-mini": {
|
||||
id: "raptor-mini",
|
||||
name: "Raptor mini",
|
||||
api: "openai-completions",
|
||||
provider: "github-copilot",
|
||||
baseUrl: "https://api.individual.githubcopilot.com",
|
||||
headers: {"User-Agent":"GitHubCopilotChat/0.35.0","Editor-Version":"vscode/1.107.0","Editor-Plugin-Version":"copilot-chat/0.35.0","Copilot-Integration-Id":"vscode-chat"},
|
||||
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false},
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.25,
|
||||
output: 2,
|
||||
cacheRead: 0.025,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 400000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"google": {
|
||||
"gemini-2.0-flash": {
|
||||
@@ -4746,42 +4763,6 @@ export const MODELS = {
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"google-generative-ai">,
|
||||
"gemma-4-E2B-it": {
|
||||
id: "gemma-4-E2B-it",
|
||||
name: "Gemma 4 E2B IT",
|
||||
api: "google-generative-ai",
|
||||
provider: "google",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {"off":null,"minimal":"MINIMAL","low":null,"medium":null,"high":"HIGH"},
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"google-generative-ai">,
|
||||
"gemma-4-E4B-it": {
|
||||
id: "gemma-4-E4B-it",
|
||||
name: "Gemma 4 E4B IT",
|
||||
api: "google-generative-ai",
|
||||
provider: "google",
|
||||
baseUrl: "https://generativelanguage.googleapis.com/v1beta",
|
||||
reasoning: true,
|
||||
thinkingLevelMap: {"off":null,"minimal":"MINIMAL","low":null,"medium":null,"high":"HIGH"},
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"google-generative-ai">,
|
||||
},
|
||||
"google-vertex": {
|
||||
"gemini-1.5-flash": {
|
||||
@@ -6315,6 +6296,24 @@ export const MODELS = {
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kimi-k2.7-code-highspeed": {
|
||||
id: "kimi-k2.7-code-highspeed",
|
||||
name: "Kimi K2.7 Code HighSpeed",
|
||||
api: "openai-completions",
|
||||
provider: "moonshotai",
|
||||
baseUrl: "https://api.moonshot.ai/v1",
|
||||
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false},
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 1.9,
|
||||
output: 8,
|
||||
cacheRead: 0.38,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"moonshotai-cn": {
|
||||
"kimi-k2-0711-preview": {
|
||||
@@ -6443,6 +6442,42 @@ export const MODELS = {
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kimi-k2.7-code": {
|
||||
id: "kimi-k2.7-code",
|
||||
name: "Kimi K2.7 Code",
|
||||
api: "openai-completions",
|
||||
provider: "moonshotai-cn",
|
||||
baseUrl: "https://api.moonshot.cn/v1",
|
||||
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false},
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.95,
|
||||
output: 4,
|
||||
cacheRead: 0.19,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kimi-k2.7-code-highspeed": {
|
||||
id: "kimi-k2.7-code-highspeed",
|
||||
name: "Kimi K2.7 Code HighSpeed",
|
||||
api: "openai-completions",
|
||||
provider: "moonshotai-cn",
|
||||
baseUrl: "https://api.moonshot.cn/v1",
|
||||
compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false},
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 1.9,
|
||||
output: 8,
|
||||
cacheRead: 0.38,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
},
|
||||
"openai": {
|
||||
"gpt-4": {
|
||||
@@ -8110,23 +8145,6 @@ export const MODELS = {
|
||||
contextWindow: 1000000,
|
||||
maxTokens: 384000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"glm-5": {
|
||||
id: "glm-5",
|
||||
name: "GLM-5",
|
||||
api: "openai-completions",
|
||||
provider: "opencode-go",
|
||||
baseUrl: "https://opencode.ai/zen/go/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1,
|
||||
output: 3.2,
|
||||
cacheRead: 0.2,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 202752,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"glm-5.1": {
|
||||
id: "glm-5.1",
|
||||
name: "GLM-5.1",
|
||||
@@ -8144,6 +8162,23 @@ export const MODELS = {
|
||||
contextWindow: 202752,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"glm-5.2": {
|
||||
id: "glm-5.2",
|
||||
name: "GLM-5.2",
|
||||
api: "openai-completions",
|
||||
provider: "opencode-go",
|
||||
baseUrl: "https://opencode.ai/zen/go/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1.4,
|
||||
output: 4.4,
|
||||
cacheRead: 0.26,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1000000,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"kimi-k2.6": {
|
||||
id: "kimi-k2.6",
|
||||
name: "Kimi K2.6",
|
||||
@@ -8687,13 +8722,13 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.22,
|
||||
output: 0.85,
|
||||
input: 0.25,
|
||||
output: 0.7999999999999999,
|
||||
cacheRead: 0.06,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
maxTokens: 80000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"arcee-ai/trinity-mini": {
|
||||
id: "arcee-ai/trinity-mini",
|
||||
@@ -8848,6 +8883,23 @@ export const MODELS = {
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"cohere/north-mini-code:free": {
|
||||
id: "cohere/north-mini-code:free",
|
||||
name: "Cohere: North Mini Code (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 256000,
|
||||
maxTokens: 64000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"deepseek/deepseek-chat": {
|
||||
id: "deepseek/deepseek-chat",
|
||||
name: "DeepSeek: DeepSeek V3",
|
||||
@@ -9156,7 +9208,24 @@ export const MODELS = {
|
||||
cacheWrite: 0.08333333333333334,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
maxTokens: 65536,
|
||||
maxTokens: 65535,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"google/gemini-3-pro-image": {
|
||||
id: "google/gemini-3-pro-image",
|
||||
name: "Google: Nano Banana Pro (Gemini 3 Pro Image)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 2,
|
||||
output: 12,
|
||||
cacheRead: 0.19999999999999998,
|
||||
cacheWrite: 0.375,
|
||||
},
|
||||
contextWindow: 65536,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"google/gemini-3.1-flash-lite": {
|
||||
id: "google/gemini-3.1-flash-lite",
|
||||
@@ -9343,7 +9412,7 @@ export const MODELS = {
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
maxTokens: 8192,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"ibm-granite/granite-4.1-8b": {
|
||||
id: "ibm-granite/granite-4.1-8b",
|
||||
@@ -9448,6 +9517,23 @@ export const MODELS = {
|
||||
contextWindow: 256000,
|
||||
maxTokens: 80000,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"liquid/lfm-2.5-1.2b-thinking:free": {
|
||||
id: "liquid/lfm-2.5-1.2b-thinking:free",
|
||||
name: "LiquidAI: LFM2.5-1.2B-Thinking (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 32768,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-70b-instruct": {
|
||||
id: "meta-llama/llama-3.1-70b-instruct",
|
||||
name: "Meta: Llama 3.1 70B Instruct",
|
||||
@@ -10018,9 +10104,9 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.6799999999999999,
|
||||
output: 3.41,
|
||||
cacheRead: 0.33999999999999997,
|
||||
input: 0.66,
|
||||
output: 3.5,
|
||||
cacheRead: 0.33,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
@@ -10035,9 +10121,9 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.75,
|
||||
output: 3.5,
|
||||
cacheRead: 0.16,
|
||||
input: 0.612,
|
||||
output: 3.0690000000000004,
|
||||
cacheRead: 0.1296,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
@@ -10172,8 +10258,8 @@ export const MODELS = {
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.5,
|
||||
output: 2.5,
|
||||
cacheRead: 0.15,
|
||||
output: 2.2,
|
||||
cacheRead: 0.09999999999999999,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1000000,
|
||||
@@ -10988,7 +11074,7 @@ export const MODELS = {
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 8192,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"openai/gpt-oss-safeguard-20b": {
|
||||
id: "openai/gpt-oss-safeguard-20b",
|
||||
@@ -11001,7 +11087,7 @@ export const MODELS = {
|
||||
cost: {
|
||||
input: 0.075,
|
||||
output: 0.3,
|
||||
cacheRead: 0.037,
|
||||
cacheRead: 0.0375,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
@@ -11211,6 +11297,23 @@ export const MODELS = {
|
||||
contextWindow: 1048756,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"poolside/laguna-m.1": {
|
||||
id: "poolside/laguna-m.1",
|
||||
name: "Poolside: Laguna M.1",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.19999999999999998,
|
||||
output: 0.39999999999999997,
|
||||
cacheRead: 0.09999999999999999,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"poolside/laguna-m.1:free": {
|
||||
id: "poolside/laguna-m.1:free",
|
||||
name: "Poolside: Laguna M.1 (free)",
|
||||
@@ -11228,6 +11331,23 @@ export const MODELS = {
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"poolside/laguna-xs.2": {
|
||||
id: "poolside/laguna-xs.2",
|
||||
name: "Poolside: Laguna XS.2",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.09999999999999999,
|
||||
output: 0.19999999999999998,
|
||||
cacheRead: 0.049999999999999996,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"poolside/laguna-xs.2:free": {
|
||||
id: "poolside/laguna-xs.2:free",
|
||||
name: "Poolside: Laguna XS.2 (free)",
|
||||
@@ -11279,6 +11399,23 @@ export const MODELS = {
|
||||
contextWindow: 131072,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"qwen/qwen-2.5-7b-instruct": {
|
||||
id: "qwen/qwen-2.5-7b-instruct",
|
||||
name: "Qwen: Qwen2.5 7B Instruct",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.04,
|
||||
output: 0.09999999999999999,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"qwen/qwen-plus": {
|
||||
id: "qwen/qwen-plus",
|
||||
name: "Qwen: Qwen-Plus",
|
||||
@@ -11834,7 +11971,7 @@ export const MODELS = {
|
||||
cost: {
|
||||
input: 0.14,
|
||||
output: 1,
|
||||
cacheRead: 0.049999999999999996,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
@@ -11849,13 +11986,13 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.39,
|
||||
output: 2.34,
|
||||
input: 0.385,
|
||||
output: 2.4499999999999997,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"qwen/qwen3.5-9b": {
|
||||
id: "qwen/qwen3.5-9b",
|
||||
@@ -11951,9 +12088,9 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.15,
|
||||
input: 0.14,
|
||||
output: 1,
|
||||
cacheRead: 0.049999999999999996,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
@@ -12248,23 +12385,6 @@ export const MODELS = {
|
||||
contextWindow: 256000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"xiaomi/mimo-v2-flash": {
|
||||
id: "xiaomi/mimo-v2-flash",
|
||||
name: "Xiaomi: MiMo-V2-Flash",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.09999999999999999,
|
||||
output: 0.3,
|
||||
cacheRead: 0.01,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"xiaomi/mimo-v2.5": {
|
||||
id: "xiaomi/mimo-v2.5",
|
||||
name: "Xiaomi: MiMo-V2.5",
|
||||
@@ -12325,13 +12445,13 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.125,
|
||||
input: 0.13,
|
||||
output: 0.85,
|
||||
cacheRead: 0.06,
|
||||
cacheRead: 0.024999999999999998,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131070,
|
||||
maxTokens: 98304,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"z-ai/glm-4.5v": {
|
||||
id: "z-ai/glm-4.5v",
|
||||
@@ -12463,11 +12583,28 @@ export const MODELS = {
|
||||
cost: {
|
||||
input: 0.98,
|
||||
output: 3.08,
|
||||
cacheRead: 0.182,
|
||||
cacheRead: 0.49,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 202752,
|
||||
maxTokens: 4096,
|
||||
maxTokens: 65535,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"z-ai/glm-5.2": {
|
||||
id: "z-ai/glm-5.2",
|
||||
name: "Z.ai: GLM 5.2",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1.2,
|
||||
output: 4.1,
|
||||
cacheRead: 0.19999999999999998,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1048576,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"~anthropic/claude-fable-latest": {
|
||||
id: "~anthropic/claude-fable-latest",
|
||||
@@ -12580,9 +12717,9 @@ export const MODELS = {
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.6799999999999999,
|
||||
output: 3.41,
|
||||
cacheRead: 0.33999999999999997,
|
||||
input: 0.66,
|
||||
output: 3.5,
|
||||
cacheRead: 0.33,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
@@ -12765,8 +12902,8 @@ export const MODELS = {
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 2.5,
|
||||
output: 7.5,
|
||||
input: 1.25,
|
||||
output: 3.75,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
@@ -14421,13 +14558,13 @@ export const MODELS = {
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.02,
|
||||
output: 0.04,
|
||||
input: 0.15,
|
||||
output: 0.15,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
contextWindow: 128000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"mistral/mistral-small": {
|
||||
id: "mistral/mistral-small",
|
||||
@@ -14565,6 +14702,23 @@ export const MODELS = {
|
||||
contextWindow: 256000,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"moonshotai/kimi-k2.7-code-highspeed": {
|
||||
id: "moonshotai/kimi-k2.7-code-highspeed",
|
||||
name: "Kimi K2.7 Code High Speed",
|
||||
api: "anthropic-messages",
|
||||
provider: "vercel-ai-gateway",
|
||||
baseUrl: "https://ai-gateway.vercel.sh",
|
||||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 1.9,
|
||||
output: 8,
|
||||
cacheRead: 0.38,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262144,
|
||||
maxTokens: 32768,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"nvidia/nemotron-3-super-120b-a12b": {
|
||||
id: "nvidia/nemotron-3-super-120b-a12b",
|
||||
name: "NVIDIA Nemotron 3 Super 120B A12B",
|
||||
@@ -15336,8 +15490,8 @@ export const MODELS = {
|
||||
cost: {
|
||||
input: 0.09,
|
||||
output: 0.3,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0.02,
|
||||
cacheRead: 0.02,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 262114,
|
||||
maxTokens: 262114,
|
||||
@@ -15801,6 +15955,23 @@ export const MODELS = {
|
||||
contextWindow: 202800,
|
||||
maxTokens: 64000,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"zai/glm-5.2": {
|
||||
id: "zai/glm-5.2",
|
||||
name: "GLM 5.2",
|
||||
api: "anthropic-messages",
|
||||
provider: "vercel-ai-gateway",
|
||||
baseUrl: "https://ai-gateway.vercel.sh",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 1.5,
|
||||
output: 4.5,
|
||||
cacheRead: 0.3,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 1000000,
|
||||
maxTokens: 128000,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"zai/glm-5v-turbo": {
|
||||
id: "zai/glm-5v-turbo",
|
||||
name: "GLM 5V Turbo",
|
||||
|
||||
Reference in New Issue
Block a user