chore: sync latest workspace updates
Some checks failed
CI / build-check-test (push) Has been cancelled
Some checks failed
CI / build-check-test (push) Has been cancelled
This commit is contained in:
@@ -1,44 +1,48 @@
|
|||||||
/**
|
/**
|
||||||
* sproutclaw / mengya 命令安装扩展
|
* sproutclaw / mengya 命令安装扩展
|
||||||
*
|
*
|
||||||
* 启动后自动在 /usr/local/bin/ 下创建 mengya 和 sproutclaw 命令:
|
* 在 Linux 和 Windows 上分别创建/更新以下命令:
|
||||||
* - mengya:源码版(./pi-test.sh,tsx + src)
|
* - mengya:源码版(Linux: ./pi-test.sh,Windows: pi-test.bat)
|
||||||
* - sproutclaw:构建版(./pi-built.sh,node dist/cli.js)
|
* - sproutclaw:构建版(Linux: ./pi-built.sh,Windows: pi-built.bat)
|
||||||
|
*
|
||||||
|
* Linux 命令安装到 /usr/local/bin/。
|
||||||
|
* Windows 批处理文件创建在项目根目录,需要把项目根目录加入 PATH 后全局使用。
|
||||||
*
|
*
|
||||||
* 命令 /install-commands 可随时手动重装。
|
* 命令 /install-commands 可随时手动重装。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, writeFileSync, chmodSync, readFileSync } from "node:fs";
|
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";
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||||
|
|
||||||
const SPROUTCLAW_DIR = "/shumengya/project/agent/sproutclaw";
|
const extensionDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
||||||
const BIN_DIR = "/usr/local/bin";
|
const SPROUTCLAW_DIR = dirname(dirname(dirname(dirname(extensionDir))));
|
||||||
|
const LINUX_BIN_DIR = "/usr/local/bin";
|
||||||
|
|
||||||
const COMMAND_LAUNCHERS: Record<string, string> = {
|
// ---------------------------- Linux scripts ----------------------------
|
||||||
mengya: "./pi-test.sh",
|
|
||||||
};
|
|
||||||
|
|
||||||
function commandScript(launcher: string): string {
|
function linuxMengyaScript(): string {
|
||||||
return `#!/usr/bin/env bash
|
return `#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd ${SPROUTCLAW_DIR}
|
cd "${SPROUTCLAW_DIR}"
|
||||||
exec ${launcher} "$@"
|
exec ./pi-test.sh "$@"
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sproutclawScript(): string {
|
function linuxSproutclawScript(): string {
|
||||||
return `#!/usr/bin/env bash
|
return `#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd ${SPROUTCLAW_DIR}
|
cd "${SPROUTCLAW_DIR}"
|
||||||
|
|
||||||
case "\${1:-}" in
|
case "\${1:-}" in
|
||||||
\tbuild)
|
\tbuild)
|
||||||
\t\tshift
|
\t\tshift
|
||||||
\t\texec npm run build "\$@"
|
\t\texec npm run build "$@"
|
||||||
\t\t;;
|
\t\t;;
|
||||||
\trun)
|
\trun)
|
||||||
\t\tshift
|
\t\tshift
|
||||||
\t\texec ./pi-built.sh "\$@"
|
\t\texec ./pi-built.sh "$@"
|
||||||
\t\t;;
|
\t\t;;
|
||||||
\thelp|-h|--help)
|
\thelp|-h|--help)
|
||||||
\t\tcat <<'EOF'
|
\t\tcat <<'EOF'
|
||||||
@@ -53,66 +57,133 @@ sproutclaw - SproutClaw pi 构建版启动器
|
|||||||
EOF
|
EOF
|
||||||
\t\t;;
|
\t\t;;
|
||||||
\t*)
|
\t*)
|
||||||
\t\texec ./pi-built.sh "\$@"
|
\t\texec ./pi-built.sh "$@"
|
||||||
\t\t;;
|
\t\t;;
|
||||||
esac
|
esac
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建或修复命令脚本 */
|
// ---------------------------- Windows scripts ----------------------------
|
||||||
function installCommand(name: string, script: string): boolean {
|
|
||||||
const path = `${BIN_DIR}/${name}`;
|
|
||||||
|
|
||||||
|
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)) {
|
if (existsSync(path)) {
|
||||||
try {
|
try {
|
||||||
const current = readFileSync(path, "utf-8");
|
const current = readFileSync(path, "utf-8");
|
||||||
if (current === script) return false;
|
if (current === content) return false;
|
||||||
} catch {
|
} catch {
|
||||||
// Rewrite unreadable or invalid command files below.
|
// Rewrite unreadable or invalid files below.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeFileSync(path, script, "utf-8");
|
writeFileSync(path, content, "utf-8");
|
||||||
chmodSync(path, 0o755);
|
if (mode !== undefined) chmodSync(path, mode);
|
||||||
return true;
|
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) {
|
export default function (pi: ExtensionAPI) {
|
||||||
if (process.platform === "win32") {
|
const isWindows = process.platform === "win32";
|
||||||
// On Windows, /usr/local/bin doesn't exist. Use pi-built.bat from the repo root instead.
|
|
||||||
pi.registerCommand("install-commands", {
|
function install(): string[] {
|
||||||
description: "N/A on Windows: use pi-built.bat from the repo root",
|
return isWindows ? installWindowsCommands() : installLinuxCommands();
|
||||||
handler: async (_args, ctx) => {
|
|
||||||
ctx.ui.notify("Windows 下无需安装全局命令,请直接使用项目根目录的 pi-built.bat", "info");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name, launcher] of Object.entries(COMMAND_LAUNCHERS)) {
|
const created = install();
|
||||||
const created = installCommand(name, commandScript(launcher));
|
for (const path of created) {
|
||||||
if (created) {
|
console.log(`[sproutclaw-setup] Created/updated ${path}`);
|
||||||
console.log(`[sproutclaw-setup] Created /usr/local/bin/${name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sproutclawCreated = installCommand("sproutclaw", sproutclawScript());
|
|
||||||
if (sproutclawCreated) {
|
|
||||||
console.log("[sproutclaw-setup] Created /usr/local/bin/sproutclaw");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pi.registerCommand("install-commands", {
|
pi.registerCommand("install-commands", {
|
||||||
description: "Install mengya (source) and sproutclaw (built) commands to /usr/local/bin",
|
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) => {
|
handler: async (_args, ctx) => {
|
||||||
let count = 0;
|
const paths = install();
|
||||||
for (const [name, launcher] of Object.entries(COMMAND_LAUNCHERS)) {
|
if (paths.length > 0) {
|
||||||
if (installCommand(name, commandScript(launcher))) count++;
|
ctx.ui.notify(`Installed/updated ${paths.length} command(s):\n${paths.join("\n")}`, "success");
|
||||||
}
|
|
||||||
if (installCommand("sproutclaw", sproutclawScript())) count++;
|
|
||||||
if (count > 0) {
|
|
||||||
ctx.ui.notify(`Installed ${count} command(s): mengya (source), sproutclaw (build/run)`, "success");
|
|
||||||
} else {
|
} else {
|
||||||
ctx.ui.notify("Commands already up to date", "info");
|
ctx.ui.notify("Commands already up to date", "info");
|
||||||
}
|
}
|
||||||
|
if (isWindows) {
|
||||||
|
ctx.ui.notify(`请把项目目录加入 PATH 以全局使用:\n${SPROUTCLAW_DIR}`, "info");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
114
.pi/agent/skills/quark-sign-skill/SKILL.md
Normal file
114
.pi/agent/skills/quark-sign-skill/SKILL.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
|
```
|
||||||
3
.pi/agent/skills/quark-sign-skill/config.json
Normal file
3
.pi/agent/skills/quark-sign-skill/config.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
3
.pi/agent/skills/quark-sign-skill/config.json.example
Normal file
3
.pi/agent/skills/quark-sign-skill/config.json.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"url": "https://drive-m.quark.cn/1/clouddrive/capacity/growth/reward_record?kps=...&sign=...&vcode=..."
|
||||||
|
}
|
||||||
178
.pi/agent/skills/quark-sign-skill/scripts/quark_sign.py
Normal file
178
.pi/agent/skills/quark-sign-skill/scripts/quark_sign.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
#!/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()
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
---
|
|
||||||
name: add-llm-provider
|
|
||||||
description: Checklist for adding a new LLM provider to packages/ai. Covers core types, provider implementation, lazy registration, model generation, the full test matrix, coding-agent wiring, and docs.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Adding a New LLM Provider (packages/ai)
|
|
||||||
|
|
||||||
A new provider touches multiple files. Work through these steps in order.
|
|
||||||
|
|
||||||
## 1. Core Types (`packages/ai/src/types.ts`)
|
|
||||||
|
|
||||||
- Add API identifier to `Api` type union (e.g. `"bedrock-converse-stream"`).
|
|
||||||
- Create options interface extending `StreamOptions`.
|
|
||||||
- Add mapping to `ApiOptionsMap`.
|
|
||||||
- Add provider name to `KnownProvider` type union.
|
|
||||||
|
|
||||||
## 2. Provider Implementation (`packages/ai/src/providers/`)
|
|
||||||
|
|
||||||
Create a provider file exporting:
|
|
||||||
|
|
||||||
- `stream<Provider>()` returning `AssistantMessageEventStream`.
|
|
||||||
- `streamSimple<Provider>()` for `SimpleStreamOptions` mapping.
|
|
||||||
- Provider-specific options interface.
|
|
||||||
- Message/tool conversion functions.
|
|
||||||
- Response parsing that emits standardized events (`text`, `tool_call`, `thinking`, `usage`, `stop`).
|
|
||||||
|
|
||||||
## 3. Provider Exports and Lazy Registration
|
|
||||||
|
|
||||||
- Add a package subpath export in `packages/ai/package.json` pointing at `./dist/providers/<provider>.js`.
|
|
||||||
- Add `export type` re-exports in `packages/ai/src/index.ts` for provider option types that should remain available from the root entry.
|
|
||||||
- Register the provider in `packages/ai/src/providers/register-builtins.ts` via lazy loader wrappers; do not statically import provider implementation modules there.
|
|
||||||
- Add credential detection in `packages/ai/src/env-api-keys.ts`.
|
|
||||||
|
|
||||||
## 4. Model Generation (`packages/ai/scripts/generate-models.ts`)
|
|
||||||
|
|
||||||
- Add logic to fetch/parse models from the provider source.
|
|
||||||
- Map to the standardized `Model` interface.
|
|
||||||
|
|
||||||
## 5. Tests (`packages/ai/test/`)
|
|
||||||
|
|
||||||
- Always add the provider to `stream.test.ts` with at least one representative model, even if it reuses an existing API impl such as `openai-completions`.
|
|
||||||
- Add the provider to the broader matrix where applicable: `tokens.test.ts`, `abort.test.ts`, `empty.test.ts`, `context-overflow.test.ts`, `unicode-surrogate.test.ts`, `tool-call-without-result.test.ts`, `image-tool-result.test.ts`, `total-tokens.test.ts`, `cross-provider-handoff.test.ts`.
|
|
||||||
- For `cross-provider-handoff.test.ts`, add at least one provider/model pair. If the provider exposes multiple model families (e.g. GPT and Claude), add at least one pair per family.
|
|
||||||
- For non-standard auth, create a utility (e.g. `bedrock-utils.ts`) with credential detection.
|
|
||||||
|
|
||||||
## 6. Coding Agent (`packages/coding-agent/`)
|
|
||||||
|
|
||||||
- `src/core/model-resolver.ts`: add default model ID to `defaultModelPerProvider`.
|
|
||||||
- `src/core/provider-display-names.ts`: add API-key login display name so `/login` and related UI show the provider for built-in API-key auth.
|
|
||||||
- `src/cli/args.ts`: add env var documentation.
|
|
||||||
- `README.md`: add provider setup instructions.
|
|
||||||
- `docs/providers.md`: add setup instructions, env var, and `auth.json` key.
|
|
||||||
|
|
||||||
## 7. Documentation
|
|
||||||
|
|
||||||
- `packages/ai/README.md`: add to providers table, document options/auth, add env vars.
|
|
||||||
- `packages/ai/CHANGELOG.md`: add entry under `## [Unreleased]`.
|
|
||||||
Reference in New Issue
Block a user