Compare commits
10 Commits
3439db32ad
...
9a23aec097
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a23aec097 | ||
|
|
77a3546459 | ||
|
|
0be55b9064 | ||
|
|
735e4de854 | ||
|
|
50a4e45ea4 | ||
|
|
7c23358142 | ||
|
|
02bf4ddf95 | ||
|
|
b3fc827b60 | ||
|
|
06113d8429 | ||
|
|
6ab87c6b67 |
@@ -1 +1 @@
|
|||||||
2026.6.9
|
2026.7.1-2
|
||||||
|
|||||||
49
README.md
49
README.md
@@ -1,12 +1,47 @@
|
|||||||
# 🦞 U-Claw(虾盘)
|
<p align="center">
|
||||||
|
<img src="assets/readme/hero.svg" alt="U-Claw:随身携带的 AI 工作空间" width="100%" />
|
||||||
|
</p>
|
||||||
|
|
||||||
> **虾盘 — 全球首个 U 盘里运行的 AI 助手 | The world's first AI assistant that runs from a USB drive**
|
<h1 align="center">U-Claw(虾盘)</h1>
|
||||||
> **制作「插上就能用」的 AI 助手 U 盘 — 教程与源代码**
|
|
||||||
> **Build a plug-and-play AI assistant USB drive — Tutorial & Source Code**
|
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
<p align="center"><strong>把 AI 工作空间装进 U 盘:插上任意电脑,配置、记忆和工具都跟着走。</strong></p>
|
||||||
|
|
||||||
[中文](#中文) | [English](#english) | [📖 完整教程](https://u-claw.org/tutorial.html)
|
<p align="center">
|
||||||
|
<a href="https://github.com/dongsheng123132/u-claw/releases">下载便携版</a> ·
|
||||||
|
<a href="https://u-claw.org/tutorial.html">完整教程</a> ·
|
||||||
|
<a href="#快速开始制作便携版-u-盘">从源码制作</a> ·
|
||||||
|
<a href="#english">English</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-1f7a8c?style=flat-square" alt="MIT License" /></a>
|
||||||
|
<img src="https://img.shields.io/badge/Portable-USB%20first-123343?style=flat-square" alt="USB first" />
|
||||||
|
<img src="https://img.shields.io/badge/Runtime-OpenClaw-48d1c1?style=flat-square" alt="OpenClaw runtime" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> 这不是 U 盘制作工具。这个仓库就是 U 盘的文件骨架:运行一次 `setup.sh` 补齐运行时,再把整个 `portable/` 拷到盘里即可。
|
||||||
|
|
||||||
|
## 三步开始
|
||||||
|
|
||||||
|
1. 从 [Releases](https://github.com/dongsheng123132/u-claw/releases) 下载 Windows 便携版,解压到 U 盘后双击 `Windows-Start.bat`。
|
||||||
|
2. 或克隆仓库,在 `portable/` 运行 `bash setup.sh`,下载 Node.js 与 OpenClaw。
|
||||||
|
3. 首次启动在本地配置模型 Key;配置只保存在 U 盘的 `data/.openclaw/openclaw.json`。
|
||||||
|
|
||||||
|
| 你得到什么 | 为什么重要 |
|
||||||
|
| --- | --- |
|
||||||
|
| 随身 AI 工作空间 | 换电脑时带走配置、记忆与工具,不在主机残留用户数据。 |
|
||||||
|
| 本地优先 | 不绑定设备、不打指纹、不上传本地配置。 |
|
||||||
|
| 多种使用方式 | 便携 U 盘、在线一键安装、可启动 Linux U 盘各自独立。 |
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> ### 👑 同作者新品:U-King · AI 装机管家
|
||||||
|
> 一键装好 **Claude Code / Codex / OpenClaw / Hermes** 四大 AI 神器,驱动自动配好——一个 Key 直用 Claude、GPT、DeepSeek、Kimi 等 24+ 全球模型,免翻墙。绿色版仅几 MB,双击即用,还带 AI 作图 / 视频 / 本地大模型 / 多终端工作台。
|
||||||
|
>
|
||||||
|
> **免费下载:[www.u-king.org](https://www.u-king.org)**(国内镜像 [cloud.u-claw.org/uking](https://cloud.u-claw.org/uking/))
|
||||||
|
>
|
||||||
|
> *U-Claw 让 AI 住进 U 盘;U-King 帮你把全套 AI 编程神器装进电脑。*
|
||||||
|
> *EN: U-King — one-click installer for Claude Code / Codex / OpenClaw, with one API key for 24+ global models → [u-king.org](https://www.u-king.org)*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -263,6 +298,7 @@ npm install && npm run build
|
|||||||
- **抖店 / 直播带货**:提供最高佣金比例,产品已在多个直播间验证转化
|
- **抖店 / 直播带货**:提供最高佣金比例,产品已在多个直播间验证转化
|
||||||
- **代理分销**:买断或按单分润均可谈,支持定制版本
|
- **代理分销**:买断或按单分润均可谈,支持定制版本
|
||||||
- **技术合作**:有开发能力者欢迎深度合作
|
- **技术合作**:有开发能力者欢迎深度合作
|
||||||
|
- **👑 U-King 装机管家分销(新)**:[U-King](https://www.u-king.org) 软件本身免费送客户,客户按用量给 AI 模型 API 充值,你拿**持续分润**——不压货、零售后(我们负责),赚的是长期流水。适合装机店、AI 培训、社群主理人
|
||||||
|
|
||||||
有意向请微信联系(备注「代理合作」优先处理)。
|
有意向请微信联系(备注「代理合作」优先处理)。
|
||||||
|
|
||||||
@@ -530,6 +566,7 @@ U-Claw 3.0 delivers excellent user experience with very low return rates. We han
|
|||||||
- **Live commerce / TikTok shop**: Top commission rates, proven conversion in live streams
|
- **Live commerce / TikTok shop**: Top commission rates, proven conversion in live streams
|
||||||
- **Reseller / distribution**: Revenue share or wholesale, custom branded versions available
|
- **Reseller / distribution**: Revenue share or wholesale, custom branded versions available
|
||||||
- **Technical partnership**: Deep collaboration welcome for developers
|
- **Technical partnership**: Deep collaboration welcome for developers
|
||||||
|
- **👑 U-King distribution (new)**: give away [U-King](https://www.u-king.org) (free AI setup manager) — earn **recurring revenue share** on your customers' API top-ups. No inventory, we handle support
|
||||||
|
|
||||||
Interested? WeChat hecare888 (mention "partnership" for priority response).
|
Interested? WeChat hecare888 (mention "partnership" for priority response).
|
||||||
|
|
||||||
|
|||||||
32
assets/readme/hero.svg
Normal file
32
assets/readme/hero.svg
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 560" role="img" aria-labelledby="title desc">
|
||||||
|
<title id="title">U-Claw portable AI workspace</title>
|
||||||
|
<desc id="desc">A USB drive carries a local AI workspace across Windows, macOS, and Linux.</desc>
|
||||||
|
<rect width="1280" height="560" fill="#09121e"/>
|
||||||
|
<path d="M0 430C196 362 286 473 491 400s300-37 486-87 207-36 303-11v258H0Z" fill="#0e2632"/>
|
||||||
|
<circle cx="1047" cy="126" r="176" fill="#163d45" opacity=".62"/>
|
||||||
|
<circle cx="1047" cy="126" r="132" fill="#1d6970" opacity=".48"/>
|
||||||
|
<g fill="none" stroke="#48d1c1" stroke-width="2" opacity=".65">
|
||||||
|
<path d="M730 92h174v74h120v104"/>
|
||||||
|
<path d="M778 387h115v-69h151v-94"/>
|
||||||
|
<path d="M235 401h114v-77h96"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(472 132)">
|
||||||
|
<rect x="0" y="0" width="286" height="260" rx="18" fill="#d8f2e9"/>
|
||||||
|
<rect x="28" y="24" width="230" height="156" rx="10" fill="#123343"/>
|
||||||
|
<path d="M113 64v58m30-58v58m30-58v58" stroke="#48d1c1" stroke-width="9" stroke-linecap="round"/>
|
||||||
|
<path d="M76 130h137" stroke="#48d1c1" stroke-width="9" stroke-linecap="round"/>
|
||||||
|
<rect x="93" y="203" width="100" height="20" rx="10" fill="#0a1b2b"/>
|
||||||
|
<path d="M44 260v38h198v-38" fill="#b9dfd5"/>
|
||||||
|
</g>
|
||||||
|
<g fill="#b8f4e8">
|
||||||
|
<text x="84" y="151" font-family="Arial, sans-serif" font-size="62" font-weight="700">U-CLAW</text>
|
||||||
|
<text x="88" y="202" font-family="Arial, sans-serif" font-size="25" letter-spacing="3">AI THAT TRAVELS WITH YOU</text>
|
||||||
|
</g>
|
||||||
|
<g font-family="Arial, sans-serif" font-size="22" fill="#d8f2e9">
|
||||||
|
<text x="90" y="313">Portable workspace</text>
|
||||||
|
<text x="90" y="350">Local data · Your model · Your USB drive</text>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(923 367)" fill="#d8f2e9" font-family="Arial, sans-serif" font-size="18">
|
||||||
|
<text x="0" y="0">Windows</text><text x="116" y="0">macOS</text><text x="218" y="0">Linux</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -355,9 +355,11 @@ metadata: { "openclaw": { "emoji": "robot" } }
|
|||||||
|
|
||||||
| Model | Use case | Context |
|
| Model | Use case | Context |
|
||||||
|-------|----------|---------|
|
|-------|----------|---------|
|
||||||
| deepseek-chat | Daily chat | 32K |
|
| deepseek-v4-flash | Daily chat, fast and cheap | 128K |
|
||||||
| deepseek-coder | Code generation | 16K |
|
| deepseek-v4-pro | Complex reasoning, code | 128K |
|
||||||
| deepseek-reasoner | Complex reasoning | 64K |
|
|
||||||
|
> Note: the legacy names `deepseek-chat` / `deepseek-coder` / `deepseek-reasoner` are
|
||||||
|
> no longer accepted by api.deepseek.com and return HTTP 400.
|
||||||
|
|
||||||
- OpenAI-compatible API, base_url: https://api.deepseek.com
|
- OpenAI-compatible API, base_url: https://api.deepseek.com
|
||||||
- Direct access in China, API Key: https://platform.deepseek.com
|
- Direct access in China, API Key: https://platform.deepseek.com
|
||||||
@@ -494,7 +496,7 @@ if ($hasConfig) {
|
|||||||
if ([string]::IsNullOrEmpty($choice)) { $choice = "1" }
|
if ([string]::IsNullOrEmpty($choice)) { $choice = "1" }
|
||||||
|
|
||||||
$modelConfigs = @{
|
$modelConfigs = @{
|
||||||
"1" = @{ model="deepseek-chat"; baseUrl="https://api.deepseek.com/v1"; provider="custom"; label="DeepSeek API Key"; hint="Get key: https://platform.deepseek.com/api_keys"; needKey=$true }
|
"1" = @{ model="deepseek-v4-flash"; baseUrl="https://api.deepseek.com/v1"; provider="custom"; label="DeepSeek API Key"; hint="Get key: https://platform.deepseek.com/api_keys"; needKey=$true }
|
||||||
"2" = @{ model="moonshot-v1-auto"; baseUrl="https://api.moonshot.cn/v1"; provider="custom"; label="Moonshot API Key"; hint="Get key: https://platform.moonshot.cn/console/api-keys"; needKey=$true }
|
"2" = @{ model="moonshot-v1-auto"; baseUrl="https://api.moonshot.cn/v1"; provider="custom"; label="Moonshot API Key"; hint="Get key: https://platform.moonshot.cn/console/api-keys"; needKey=$true }
|
||||||
"3" = @{ model="qwen-plus"; baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"; provider="custom"; label="Qwen API Key"; hint="Get key: https://dashscope.console.aliyun.com/apiKey (free quota available)"; needKey=$true }
|
"3" = @{ model="qwen-plus"; baseUrl="https://dashscope.aliyuncs.com/compatible-mode/v1"; provider="custom"; label="Qwen API Key"; hint="Get key: https://dashscope.console.aliyun.com/apiKey (free quota available)"; needKey=$true }
|
||||||
"4" = @{ model="glm-4-plus"; baseUrl="https://open.bigmodel.cn/api/paas/v4"; provider="custom"; label="Zhipu API Key"; hint="Get key: https://open.bigmodel.cn/usercenter/apikeys"; needKey=$true }
|
"4" = @{ model="glm-4-plus"; baseUrl="https://open.bigmodel.cn/api/paas/v4"; provider="custom"; label="Zhipu API Key"; hint="Get key: https://open.bigmodel.cn/usercenter/apikeys"; needKey=$true }
|
||||||
|
|||||||
@@ -495,9 +495,11 @@ metadata: { "openclaw": { "emoji": "🤖" } }
|
|||||||
|
|
||||||
| 模型 | 适用场景 | 上下文长度 |
|
| 模型 | 适用场景 | 上下文长度 |
|
||||||
|------|---------|-----------|
|
|------|---------|-----------|
|
||||||
| deepseek-chat | 日常对话、文案写作、知识问答 | 32K |
|
| deepseek-v4-flash | 日常对话、文案写作、知识问答,快且便宜 | 128K |
|
||||||
| deepseek-coder | 代码生成、代码审查、技术文档 | 16K |
|
| deepseek-v4-pro | 复杂推理、数学、代码 | 128K |
|
||||||
| deepseek-reasoner | 复杂推理、数学、逻辑分析 | 64K |
|
|
||||||
|
> 注意:旧名 `deepseek-chat` / `deepseek-coder` / `deepseek-reasoner` 已被
|
||||||
|
> api.deepseek.com 拒绝,调用会直接返回 HTTP 400。
|
||||||
|
|
||||||
## 关键提示
|
## 关键提示
|
||||||
|
|
||||||
@@ -796,7 +798,7 @@ else
|
|||||||
# 模型配置映射
|
# 模型配置映射
|
||||||
case $MODEL_CHOICE in
|
case $MODEL_CHOICE in
|
||||||
1)
|
1)
|
||||||
MODEL_NAME="deepseek-chat"
|
MODEL_NAME="deepseek-v4-flash"
|
||||||
BASE_URL="https://api.deepseek.com/v1"
|
BASE_URL="https://api.deepseek.com/v1"
|
||||||
PROVIDER="custom"
|
PROVIDER="custom"
|
||||||
KEY_LABEL="DeepSeek API Key"
|
KEY_LABEL="DeepSeek API Key"
|
||||||
@@ -877,7 +879,7 @@ else
|
|||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e " ${YELLOW}未知选项,使用默认 DeepSeek${NC}"
|
echo -e " ${YELLOW}未知选项,使用默认 DeepSeek${NC}"
|
||||||
MODEL_NAME="deepseek-chat"
|
MODEL_NAME="deepseek-v4-flash"
|
||||||
BASE_URL="https://api.deepseek.com/v1"
|
BASE_URL="https://api.deepseek.com/v1"
|
||||||
PROVIDER="custom"
|
PROVIDER="custom"
|
||||||
KEY_LABEL="DeepSeek API Key"
|
KEY_LABEL="DeepSeek API Key"
|
||||||
|
|||||||
@@ -170,10 +170,10 @@ input:focus { border-color: #ff6b35; }
|
|||||||
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
||||||
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank" data-action-label="注册 / 充值 / 获取 API Key">→ 注册 / 充值 / 获取 API Key</a>
|
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank" data-action-label="注册 / 充值 / 获取 API Key">→ 注册 / 充值 / 获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-chat">
|
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-v4-flash">
|
||||||
<span class="check">✓</span>
|
<span class="check">✓</span>
|
||||||
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
||||||
<p>deepseek-chat / deepseek-v4,超低价格</p>
|
<p>deepseek-v4-flash / deepseek-v4-pro,超低价格</p>
|
||||||
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="minimax" data-base="https://api.minimax.chat/v1" data-model="MiniMax-M2">
|
<div class="model-card" data-provider="minimax" data-base="https://api.minimax.chat/v1" data-model="MiniMax-M2">
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ do_config() {
|
|||||||
echo ""
|
echo ""
|
||||||
echo -e " DeepSeek → 选 Custom Provider"
|
echo -e " DeepSeek → 选 Custom Provider"
|
||||||
echo -e " URL: https://api.deepseek.com/v1"
|
echo -e " URL: https://api.deepseek.com/v1"
|
||||||
echo -e " 模型: deepseek-chat"
|
echo -e " 模型: deepseek-v4-flash"
|
||||||
echo -e " Kimi → 选 Moonshot AI"
|
echo -e " Kimi → 选 Moonshot AI"
|
||||||
echo -e " 通义千问 → 选 Qwen"
|
echo -e " 通义千问 → 选 Qwen"
|
||||||
echo -e " 豆包 → 选 Volcano Engine"
|
echo -e " 豆包 → 选 Volcano Engine"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2026.6.9
|
2026.7.1-2
|
||||||
|
|||||||
@@ -477,12 +477,12 @@ echo Fetching latest version...
|
|||||||
for /f "tokens=*" %%v in ('"%NODE_BIN%" -e "const https=require('https');https.get('https://registry.npmmirror.com/openclaw/latest',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>{try{console.log(JSON.parse(d).version)}catch(e){console.log('error')}})})" 2^>nul') do set LATEST_VER=%%v
|
for /f "tokens=*" %%v in ('"%NODE_BIN%" -e "const https=require('https');https.get('https://registry.npmmirror.com/openclaw/latest',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>{try{console.log(JSON.parse(d).version)}catch(e){console.log('error')}})})" 2^>nul') do set LATEST_VER=%%v
|
||||||
|
|
||||||
if "!LATEST_VER!"=="" (
|
if "!LATEST_VER!"=="" (
|
||||||
echo Could not fetch latest version (network issue?)
|
echo Could not fetch latest version ^(network issue?^)
|
||||||
pause
|
pause
|
||||||
goto :menu
|
goto :menu
|
||||||
)
|
)
|
||||||
if "!LATEST_VER!"=="error" (
|
if "!LATEST_VER!"=="error" (
|
||||||
echo Could not fetch latest version (network issue?)
|
echo Could not fetch latest version ^(network issue?^)
|
||||||
pause
|
pause
|
||||||
goto :menu
|
goto :menu
|
||||||
)
|
)
|
||||||
@@ -550,7 +550,7 @@ if !BK_COUNT! gtr 3 (
|
|||||||
echo Cleaned !DEL_NUM! old backups.
|
echo Cleaned !DEL_NUM! old backups.
|
||||||
)
|
)
|
||||||
) else (
|
) else (
|
||||||
echo Backups: !BK_COUNT! (no cleanup needed)
|
echo Backups: !BK_COUNT! ^(no cleanup needed^)
|
||||||
)
|
)
|
||||||
|
|
||||||
REM Clean old logs (>7 days)
|
REM Clean old logs (>7 days)
|
||||||
@@ -598,7 +598,7 @@ if "%plgchoice%"=="b" (
|
|||||||
echo.
|
echo.
|
||||||
echo Common plugins:
|
echo Common plugins:
|
||||||
echo @icesword760/openclaw-wechat - WeChat
|
echo @icesword760/openclaw-wechat - WeChat
|
||||||
echo @nicepkg/openclaw-plugin-qq - QQ (community)
|
echo @nicepkg/openclaw-plugin-qq - QQ ^(community^)
|
||||||
echo.
|
echo.
|
||||||
set /p plgname=" Plugin name (empty to cancel): "
|
set /p plgname=" Plugin name (empty to cancel): "
|
||||||
if not "!plgname!"=="" (
|
if not "!plgname!"=="" (
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ for /f "usebackq tokens=1,* delims==" %%a in (`""%NODE_BIN%" "%UCLAW_DIR%lib\res
|
|||||||
)
|
)
|
||||||
if defined NO_PROXY (
|
if defined NO_PROXY (
|
||||||
set "no_proxy=%NO_PROXY%"
|
set "no_proxy=%NO_PROXY%"
|
||||||
echo Direct-connect (NO_PROXY): %NO_PROXY%
|
REM Note: no unescaped parens in echo inside this IF block - cmd treats ) as block-end.
|
||||||
|
echo Direct-connect via NO_PROXY: %NO_PROXY%
|
||||||
)
|
)
|
||||||
|
|
||||||
REM Async update check (non-blocking, 5s timeout, silent failure)
|
REM Async update check (non-blocking, 5s timeout, silent failure)
|
||||||
@@ -134,7 +135,30 @@ if exist "%WECHAT_PLUGIN_SRC%\openclaw.plugin.json" (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
REM Find available port
|
REM Start Config Server in background
|
||||||
|
echo Starting Config Center on port 18788...
|
||||||
|
set "CONFIG_SERVER=%UCLAW_DIR%config-server"
|
||||||
|
set "RUNTIME_JSON=%STATE_DIR%\runtime.json"
|
||||||
|
del "%RUNTIME_JSON%" >nul 2>&1
|
||||||
|
start /B "" "%NODE_BIN%" "%CONFIG_SERVER%\server.js" >nul 2>&1
|
||||||
|
|
||||||
|
REM Wait for Config Server with polling instead of a fixed delay.
|
||||||
|
REM It writes runtime.json with the actual fallback port when ready.
|
||||||
|
set /a CFG_TRIES=0
|
||||||
|
:wait_config
|
||||||
|
if exist "%RUNTIME_JSON%" goto :config_ready
|
||||||
|
set /a CFG_TRIES+=1
|
||||||
|
if %CFG_TRIES% geq 20 goto :config_ready
|
||||||
|
ping -n 1 -w 300 127.0.0.1 >nul 2>&1
|
||||||
|
goto :wait_config
|
||||||
|
:config_ready
|
||||||
|
set "CONFIG_PORT=18788"
|
||||||
|
if exist "%RUNTIME_JSON%" (
|
||||||
|
for /f "usebackq tokens=*" %%p in (`powershell -NoProfile -Command "try { (Get-Content -Raw '%RUNTIME_JSON%' | ConvertFrom-Json).configServerPort } catch {}" 2^>nul`) do set "CONFIG_PORT=%%p"
|
||||||
|
)
|
||||||
|
echo Config Center port: %CONFIG_PORT%
|
||||||
|
|
||||||
|
REM Find available gateway port after Config Center has bound its port.
|
||||||
set PORT=18789
|
set PORT=18789
|
||||||
:check_port
|
:check_port
|
||||||
netstat -an | findstr ":%PORT% " | findstr "LISTENING" >nul 2>&1
|
netstat -an | findstr ":%PORT% " | findstr "LISTENING" >nul 2>&1
|
||||||
@@ -152,24 +176,6 @@ if %errorlevel%==0 (
|
|||||||
echo Starting OpenClaw on port %PORT%...
|
echo Starting OpenClaw on port %PORT%...
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM Start Config Server in background
|
|
||||||
echo Starting Config Center on port 18788...
|
|
||||||
set "CONFIG_SERVER=%UCLAW_DIR%config-server"
|
|
||||||
start /B "" "%NODE_BIN%" "%CONFIG_SERVER%\server.js" >nul 2>&1
|
|
||||||
|
|
||||||
REM Wait for Config Server with polling instead of a fixed delay.
|
|
||||||
REM It is usually ready in under one second. Poll about every 0.3 seconds,
|
|
||||||
REM up to about 6 seconds, then continue anyway.
|
|
||||||
set /a CFG_TRIES=0
|
|
||||||
:wait_config
|
|
||||||
netstat -an | findstr ":18788 " | findstr "LISTENING" >nul 2>&1
|
|
||||||
if %errorlevel%==0 goto :config_ready
|
|
||||||
set /a CFG_TRIES+=1
|
|
||||||
if %CFG_TRIES% geq 20 goto :config_ready
|
|
||||||
ping -n 1 -w 300 127.0.0.1 >nul 2>&1
|
|
||||||
goto :wait_config
|
|
||||||
:config_ready
|
|
||||||
|
|
||||||
REM Do not open Dashboard before the gateway is ready.
|
REM Do not open Dashboard before the gateway is ready.
|
||||||
REM Slow USB drives may need tens of seconds to stage bundled deps.
|
REM Slow USB drives may need tens of seconds to stage bundled deps.
|
||||||
REM Open the local startup page now, and always open Config Center too.
|
REM Open the local startup page now, and always open Config Center too.
|
||||||
@@ -182,11 +188,11 @@ set "LOADING_URL=file:///%LOADING_PATH:\=/%?port=%PORT%&token=uclaw"
|
|||||||
start "" "%LOADING_URL%"
|
start "" "%LOADING_URL%"
|
||||||
|
|
||||||
echo Opening Config Center...
|
echo Opening Config Center...
|
||||||
start "" http://127.0.0.1:18788/
|
start "" http://127.0.0.1:%CONFIG_PORT%/
|
||||||
|
|
||||||
REM Fallback watcher: if the startup page cannot poll from file URLs,
|
REM Fallback watcher: if the startup page cannot poll from file URLs,
|
||||||
REM keep polling and reopen Config Center after the gateway is ready.
|
REM keep polling and reopen Config Center after the gateway is ready.
|
||||||
start /B "" cmd /c ""%UCLAW_DIR%lib\wait-gateway.bat" %PORT%"
|
start /B "" cmd /c ""%UCLAW_DIR%lib\wait-gateway.bat" %PORT% %CONFIG_PORT%"
|
||||||
|
|
||||||
REM Prewarm gateway in the background after it becomes ready.
|
REM Prewarm gateway in the background after it becomes ready.
|
||||||
start /B "" "%NODE_BIN%" "%UCLAW_DIR%lib\prewarm.mjs" %PORT% uclaw >nul 2>&1
|
start /B "" "%NODE_BIN%" "%UCLAW_DIR%lib\prewarm.mjs" %PORT% uclaw >nul 2>&1
|
||||||
|
|||||||
@@ -199,10 +199,10 @@ input:focus { border-color: #ff6b35; }
|
|||||||
<p>moonshot-v1-auto,智能选模型</p>
|
<p>moonshot-v1-auto,智能选模型</p>
|
||||||
<a class="buy-link" href="https://platform.moonshot.cn/" target="_blank">→ 获取 API Key</a>
|
<a class="buy-link" href="https://platform.moonshot.cn/" target="_blank">→ 获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-chat">
|
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-v4-flash">
|
||||||
<span class="check">✓</span>
|
<span class="check">✓</span>
|
||||||
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
||||||
<p>deepseek-chat,超低价格</p>
|
<p>deepseek-v4-flash,超低价格</p>
|
||||||
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="zai" data-base="" data-model="glm-5">
|
<div class="model-card" data-provider="zai" data-base="" data-model="glm-5">
|
||||||
@@ -678,7 +678,9 @@ async function saveChannelsAndOpen() {
|
|||||||
var channels = {};
|
var channels = {};
|
||||||
|
|
||||||
var tgToken = document.getElementById('ch-telegram-token').value.trim();
|
var tgToken = document.getElementById('ch-telegram-token').value.trim();
|
||||||
if (tgToken) channels.telegram = { enabled: true, token: tgToken, dmPolicy: 'pairing' };
|
// OpenClaw 顶层 telegram 渠道只认 botToken(旧 token 别名仅在 accounts.<id> 里有效)。
|
||||||
|
// 之前写成 token 会被静默忽略 → Telegram 接不上。
|
||||||
|
if (tgToken) channels.telegram = { enabled: true, botToken: tgToken, dmPolicy: 'pairing' };
|
||||||
|
|
||||||
var qqId = document.getElementById('ch-qqbot-appid').value.trim();
|
var qqId = document.getElementById('ch-qqbot-appid').value.trim();
|
||||||
var qqSecret = document.getElementById('ch-qqbot-secret').value.trim();
|
var qqSecret = document.getElementById('ch-qqbot-secret').value.trim();
|
||||||
|
|||||||
@@ -246,7 +246,13 @@ async function handleWeChatStatus(sessionKey) {
|
|||||||
return { status: 'expired', message: 'Session expired' };
|
return { status: 'expired', message: 'Session expired' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await pollWeChatQrStatus(login.apiBaseUrl, login.qrcode);
|
// 状态轮询用 pollBaseUrl(IDC 重定向后会指向新主机);二维码获取/刷新始终用原始
|
||||||
|
// apiBaseUrl(与官方插件一致:refresh 回到固定主机,只有 status 轮询跟随重定向)。
|
||||||
|
const result = await pollWeChatQrStatus(login.pollBaseUrl || login.apiBaseUrl, login.qrcode);
|
||||||
|
// 微信登录状态流转日志(跳过高频的 wait,便于排查"扫码卡死"类问题)。
|
||||||
|
if (result.status && result.status !== 'wait') {
|
||||||
|
console.log(`[wechat] status=${result.status}` + (result.redirect_host ? ` redirect_host=${result.redirect_host}` : ''));
|
||||||
|
}
|
||||||
|
|
||||||
if (result.status === 'expired') {
|
if (result.status === 'expired') {
|
||||||
// Try to refresh QR code
|
// Try to refresh QR code
|
||||||
@@ -261,6 +267,8 @@ async function handleWeChatStatus(sessionKey) {
|
|||||||
login.qrcode = refreshed.qrcode;
|
login.qrcode = refreshed.qrcode;
|
||||||
login.qrcodeUrl = newQr;
|
login.qrcodeUrl = newQr;
|
||||||
login.startedAt = Date.now();
|
login.startedAt = Date.now();
|
||||||
|
// 新二维码来自原始主机,重置轮询主机,避免拿新码去轮询旧的重定向主机。
|
||||||
|
login.pollBaseUrl = null;
|
||||||
return { status: 'refreshed', qrcodeUrl: newQr };
|
return { status: 'refreshed', qrcodeUrl: newQr };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +310,18 @@ async function handleWeChatStatus(sessionKey) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IDC 重定向:用户扫码后,ilink 服务端可能要求把后续轮询切换到另一个数据中心主机
|
||||||
|
// (status=scaned_but_redirect + redirect_host)。必须跟着切,否则一直轮询旧主机,
|
||||||
|
// 扫码后永远等不到 confirmed——表现为「扫了码却卡死不前进」。
|
||||||
|
// 同款逻辑见官方插件 openclaw-weixin/src/auth/login-qr.ts 的 scaned_but_redirect 分支。
|
||||||
|
if (result.status === 'scaned_but_redirect') {
|
||||||
|
if (result.redirect_host) {
|
||||||
|
login.pollBaseUrl = 'https://' + result.redirect_host;
|
||||||
|
}
|
||||||
|
// 对前端按「已扫码」处理:显示提示并继续轮询,下一轮已指向新主机。
|
||||||
|
return { status: 'scaned' };
|
||||||
|
}
|
||||||
|
|
||||||
return { status: result.status };
|
return { status: result.status };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ REM Slow USB drives can need tens of seconds before the gateway listens.
|
|||||||
REM
|
REM
|
||||||
REM Windows-Start opens Config Center immediately, so the ready path only
|
REM Windows-Start opens Config Center immediately, so the ready path only
|
||||||
REM exits quietly. On timeout, reopen Config Center as a recovery hint.
|
REM exits quietly. On timeout, reopen Config Center as a recovery hint.
|
||||||
REM Usage (called in background by Windows-Start.bat): wait-gateway.bat PORT
|
REM Usage (called in background by Windows-Start.bat): wait-gateway.bat PORT CONFIG_PORT
|
||||||
REM Polls every 2 seconds, up to about 5 minutes.
|
REM Polls every 2 seconds, up to about 5 minutes.
|
||||||
|
|
||||||
set "PORT=%~1"
|
set "PORT=%~1"
|
||||||
if "%PORT%"=="" set "PORT=18789"
|
if "%PORT%"=="" set "PORT=18789"
|
||||||
|
set "CONFIG_PORT=%~2"
|
||||||
|
if "%CONFIG_PORT%"=="" set "CONFIG_PORT=18788"
|
||||||
|
|
||||||
set /a TRIES=0
|
set /a TRIES=0
|
||||||
:wait_loop
|
:wait_loop
|
||||||
@@ -16,12 +18,15 @@ netstat -an | findstr ":%PORT% " | findstr "LISTENING" >nul 2>&1
|
|||||||
if %errorlevel%==0 goto :ready
|
if %errorlevel%==0 goto :ready
|
||||||
set /a TRIES+=1
|
set /a TRIES+=1
|
||||||
if %TRIES% geq 150 goto :timeout
|
if %TRIES% geq 150 goto :timeout
|
||||||
timeout /t 2 /nobreak >nul
|
REM Use ping (not "timeout /t") for the ~2s delay: if a GNU coreutils "timeout"
|
||||||
|
REM is on PATH (Git/MSYS bin), it rejects "/t" and this poll becomes a busy-loop.
|
||||||
|
REM ping for the delay is PATH-robust and matches Windows-Start.bat's wait loop.
|
||||||
|
ping -n 3 127.0.0.1 >nul 2>&1
|
||||||
goto :wait_loop
|
goto :wait_loop
|
||||||
|
|
||||||
:ready
|
:ready
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|
||||||
:timeout
|
:timeout
|
||||||
start "" http://127.0.0.1:18788/
|
start "" http://127.0.0.1:%CONFIG_PORT%/
|
||||||
exit /b 1
|
exit /b 1
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ metadata: { "openclaw": { "emoji": "🤖" } }
|
|||||||
|
|
||||||
## 功能概述
|
## 功能概述
|
||||||
|
|
||||||
- **模型选择指南**: deepseek-chat vs deepseek-coder 的使用场景对比
|
- **模型选择指南**: deepseek-v4-flash vs deepseek-v4-pro 的使用场景对比
|
||||||
- **API 调用示例**: 提供 Python、curl、Node.js 等语言的调用代码
|
- **API 调用示例**: 提供 Python、curl、Node.js 等语言的调用代码
|
||||||
- **定价计算**: 根据 token 用量估算费用(输入 ¥1/百万tokens,输出 ¥2/百万tokens)
|
- **定价计算**: 根据 token 用量估算费用(输入 ¥1/百万tokens,输出 ¥2/百万tokens)
|
||||||
- **Prompt 优化**: 针对 DeepSeek 模型特性优化提示词
|
- **Prompt 优化**: 针对 DeepSeek 模型特性优化提示词
|
||||||
@@ -20,9 +20,12 @@ metadata: { "openclaw": { "emoji": "🤖" } }
|
|||||||
|
|
||||||
| 模型 | 适用场景 | 上下文长度 |
|
| 模型 | 适用场景 | 上下文长度 |
|
||||||
|------|---------|-----------|
|
|------|---------|-----------|
|
||||||
| deepseek-chat | 日常对话、文案写作、知识问答 | 32K |
|
| deepseek-v4-flash | 日常对话、文案写作、知识问答,快且便宜 | 128K |
|
||||||
| deepseek-coder | 代码生成、代码审查、技术文档 | 16K |
|
| deepseek-v4-pro | 复杂推理、数学、代码生成与审查 | 128K |
|
||||||
| deepseek-reasoner | 复杂推理、数学、逻辑分析 | 64K |
|
|
||||||
|
> ⚠️ 旧名 `deepseek-chat` / `deepseek-coder` / `deepseek-reasoner` 已下线,
|
||||||
|
> api.deepseek.com 现在只接受 `deepseek-v4-flash` 和 `deepseek-v4-pro`,
|
||||||
|
> 用旧名调用会直接返回 HTTP 400。
|
||||||
|
|
||||||
## 使用示例
|
## 使用示例
|
||||||
|
|
||||||
@@ -33,7 +36,7 @@ metadata: { "openclaw": { "emoji": "🤖" } }
|
|||||||
|
|
||||||
### 模型选择
|
### 模型选择
|
||||||
```
|
```
|
||||||
我要做一个客服机器人,应该用 deepseek-chat 还是 deepseek-coder?
|
我要做一个客服机器人,应该用 deepseek-v4-flash 还是 deepseek-v4-pro?
|
||||||
```
|
```
|
||||||
|
|
||||||
### 费用估算
|
### 费用估算
|
||||||
@@ -49,7 +52,7 @@ metadata: { "openclaw": { "emoji": "🤖" } }
|
|||||||
## 适用场景
|
## 适用场景
|
||||||
|
|
||||||
- 首次接入 DeepSeek API 的开发者
|
- 首次接入 DeepSeek API 的开发者
|
||||||
- 需要在 deepseek-chat 和 deepseek-coder 之间做选择
|
- 需要在 deepseek-v4-flash 和 deepseek-v4-pro 之间做选择
|
||||||
- 想要优化 API 调用成本
|
- 想要优化 API 调用成本
|
||||||
- 排查 API 调用问题
|
- 排查 API 调用问题
|
||||||
- 需要快速生成 API 调用代码片段
|
- 需要快速生成 API 调用代码片段
|
||||||
|
|||||||
68
tests/wechat-login-redirect.test.mjs
Normal file
68
tests/wechat-login-redirect.test.mjs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
|
||||||
|
const repoRoot = fileURLToPath(new URL('..', import.meta.url));
|
||||||
|
const server = readFileSync(join(repoRoot, 'portable', 'config-server', 'server.js'), 'utf8');
|
||||||
|
const configUi = readFileSync(join(repoRoot, 'portable', 'config-server', 'public', 'index.html'), 'utf8');
|
||||||
|
|
||||||
|
// The ilink QR API (StatusResponse in openclaw-weixin/src/auth/login-qr.ts) can return
|
||||||
|
// status "scaned_but_redirect" with a redirect_host: after the user scans, polling must
|
||||||
|
// move to a new IDC host or "confirmed" never arrives and the QR screen hangs forever.
|
||||||
|
// The config-server must mirror the plugin's redirect handling.
|
||||||
|
|
||||||
|
test('config-server follows the WeChat scaned_but_redirect IDC redirect', () => {
|
||||||
|
// Status polling uses a redirect-aware host, not the fixed apiBaseUrl directly.
|
||||||
|
assert.match(
|
||||||
|
server,
|
||||||
|
/pollWeChatQrStatus\(\s*login\.pollBaseUrl\s*\|\|\s*login\.apiBaseUrl/,
|
||||||
|
'status polling must use login.pollBaseUrl (falls back to apiBaseUrl)',
|
||||||
|
);
|
||||||
|
|
||||||
|
// On scaned_but_redirect, switch the poll host to redirect_host.
|
||||||
|
assert.match(
|
||||||
|
server,
|
||||||
|
/scaned_but_redirect[\s\S]{0,200}login\.pollBaseUrl\s*=\s*['"]https:\/\/['"]\s*\+\s*result\.redirect_host/,
|
||||||
|
'must set login.pollBaseUrl to https://<redirect_host> on scaned_but_redirect',
|
||||||
|
);
|
||||||
|
|
||||||
|
// The redirect case is reported to the client as "scaned" so it keeps polling.
|
||||||
|
assert.match(
|
||||||
|
server,
|
||||||
|
/scaned_but_redirect[\s\S]{0,260}return\s*\{\s*status:\s*['"]scaned['"]\s*\}/,
|
||||||
|
'scaned_but_redirect should surface as status "scaned" to the client',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config-server writes the Telegram bot token under the field OpenClaw reads (botToken)', () => {
|
||||||
|
// OpenClaw's top-level telegram channel schema only reads `botToken` (the legacy
|
||||||
|
// `token` alias is honored only inside accounts.<id>). Writing flat `token` silently
|
||||||
|
// disables Telegram, so the Config Center must write `botToken`.
|
||||||
|
assert.match(
|
||||||
|
configUi,
|
||||||
|
/channels\.telegram\s*=\s*\{[^}]*botToken:\s*tgToken/,
|
||||||
|
'telegram channel must be saved with botToken',
|
||||||
|
);
|
||||||
|
assert.doesNotMatch(
|
||||||
|
configUi,
|
||||||
|
/channels\.telegram\s*=\s*\{[^}]*\btoken:\s*tgToken/,
|
||||||
|
'telegram channel must not use the flat `token` field (ignored by OpenClaw)',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config-server resets the poll host when the QR is refreshed', () => {
|
||||||
|
// A refreshed QR comes from the original host, so the redirected poll host must reset,
|
||||||
|
// otherwise the new QR would be polled against a stale redirect host.
|
||||||
|
assert.match(
|
||||||
|
server,
|
||||||
|
/status:\s*'refreshed'[\s\S]{0,400}/,
|
||||||
|
'refresh branch should exist',
|
||||||
|
);
|
||||||
|
assert.match(
|
||||||
|
server,
|
||||||
|
/login\.pollBaseUrl\s*=\s*null;[\s\S]{0,200}status:\s*'refreshed'/,
|
||||||
|
'QR refresh must reset login.pollBaseUrl before returning refreshed',
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -50,7 +50,7 @@ test('Windows startup keeps Config Center available even after model setup', ()
|
|||||||
|
|
||||||
assert.match(
|
assert.match(
|
||||||
script,
|
script,
|
||||||
/Opening Config Center[\s\S]*start "" http:\/\/127\.0\.0\.1:18788\//,
|
/Opening Config Center[\s\S]*start "" http:\/\/127\.0\.0\.1:%CONFIG_PORT%\//,
|
||||||
'Windows-Start.bat should always open Config Center for model/channel changes',
|
'Windows-Start.bat should always open Config Center for model/channel changes',
|
||||||
);
|
);
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
@@ -65,7 +65,7 @@ test('Windows gateway fallback does not force-open Dashboard', () => {
|
|||||||
|
|
||||||
assert.match(
|
assert.match(
|
||||||
script,
|
script,
|
||||||
/:timeout[\s\S]*start "" http:\/\/127\.0\.0\.1:18788\//,
|
/:timeout[\s\S]*start "" http:\/\/127\.0\.0\.1:%CONFIG_PORT%\//,
|
||||||
'wait-gateway.bat should return users to Config Center on timeout',
|
'wait-gateway.bat should return users to Config Center on timeout',
|
||||||
);
|
);
|
||||||
assert.doesNotMatch(
|
assert.doesNotMatch(
|
||||||
@@ -147,6 +147,48 @@ test('OpenClaw doctor launcher is read-only (no destructive repair flags)', () =
|
|||||||
assert.doesNotMatch(bat, /doctor[^\n]*--force/);
|
assert.doesNotMatch(bat, /doctor[^\n]*--force/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// cmd.exe treats ')' as the end of an IF/FOR ( ... ) block, so an unescaped paren in
|
||||||
|
// an `echo` *inside* a block aborts parsing ("was unexpected at this time") and the
|
||||||
|
// window flash-closes. This shipped once in v2.1.10 (echo Direct-connect (NO_PROXY)).
|
||||||
|
// Track block depth structurally and flag any unescaped ( or ) in echoes inside a block.
|
||||||
|
function unescapedParenEchoesInsideBlocks(bat) {
|
||||||
|
const offenders = [];
|
||||||
|
let depth = 0;
|
||||||
|
for (const raw of bat.split(/\r?\n/)) {
|
||||||
|
const line = raw.trim();
|
||||||
|
if (depth > 0 && /^echo\b/i.test(line)) {
|
||||||
|
// cmd eats an unescaped ')' that sits at the END of an echo (cosmetic: the ')'
|
||||||
|
// just disappears). The flash-exit only happens when a ')' is followed by more
|
||||||
|
// text on the line (e.g. "(NO_PROXY): value" → ')' closes the block, ": value"
|
||||||
|
// then errors). So: drop escaped ^), drop trailing ')'/whitespace, and flag any
|
||||||
|
// ')' that survives (meaning it had text after it).
|
||||||
|
let s = line.replace(/\^\)/g, '').replace(/[)\s]+$/, '');
|
||||||
|
if (s.includes(')')) offenders.push(raw.trim());
|
||||||
|
}
|
||||||
|
// structural depth: ') else (' keeps depth; leading ')' closes; if/for line ending in '(' opens
|
||||||
|
if (/^\)\s*else\b.*\($/i.test(line)) { /* same depth */ }
|
||||||
|
else if (/^\)/.test(line)) depth = Math.max(0, depth - 1);
|
||||||
|
if (/^\($/.test(line) || /\b(if|for)\b.*[^^]\(\s*$/i.test(line)) depth += 1;
|
||||||
|
}
|
||||||
|
return offenders;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Windows launchers have no unescaped parens in echoes inside IF/FOR blocks (v2.1.10 flash-exit regression)', () => {
|
||||||
|
for (const name of [
|
||||||
|
'Windows-Start.bat',
|
||||||
|
'Windows-IntranetFix.bat',
|
||||||
|
'Windows-LocalModel.bat',
|
||||||
|
'OpenClaw-Doctor.bat',
|
||||||
|
'Windows-Diagnose.bat',
|
||||||
|
'Windows-Menu.bat',
|
||||||
|
'Windows-Install.bat',
|
||||||
|
]) {
|
||||||
|
const bat = readRepoFile('portable', name);
|
||||||
|
const offenders = unescapedParenEchoesInsideBlocks(bat);
|
||||||
|
assert.deepEqual(offenders, [], `${name} has unescaped parens in block echo(es): ${offenders.join(' | ')}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('PowerShell installer generated start.bat disables OpenClaw bonjour discovery', () => {
|
test('PowerShell installer generated start.bat disables OpenClaw bonjour discovery', () => {
|
||||||
const script = readRepoFile('install', 'install.ps1');
|
const script = readRepoFile('install', 'install.ps1');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "u-claw",
|
"name": "u-claw",
|
||||||
"version": "2.1.10",
|
"version": "2.1.17",
|
||||||
"description": "U-Claw - AI 助手桌面版,插上 U 盘就能用",
|
"description": "U-Claw - AI 助手桌面版,插上 U 盘就能用",
|
||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"author": "U-Claw <hello@u-claw.org> (https://u-claw.org)",
|
"author": "U-Claw <hello@u-claw.org> (https://u-claw.org)",
|
||||||
|
|||||||
@@ -170,10 +170,10 @@ input:focus { border-color: #ff6b35; }
|
|||||||
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
<p>一个 Key 用 DeepSeek / Claude / GPT / 通义 等国内外全部大模型,无需翻墙</p>
|
||||||
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank">→ 注册并获取 API Key</a>
|
<a class="buy-link" href="https://u-claw.org/cloud.html" target="_blank">→ 注册并获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-chat">
|
<div class="model-card" data-provider="deepseek" data-base="https://api.deepseek.com/v1" data-model="deepseek-v4-flash">
|
||||||
<span class="check">✓</span>
|
<span class="check">✓</span>
|
||||||
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
<h4>DeepSeek <span class="tag cn">国内</span><span class="tag cheap">便宜</span></h4>
|
||||||
<p>deepseek-chat / deepseek-v4,超低价格</p>
|
<p>deepseek-v4-flash / deepseek-v4-pro,超低价格</p>
|
||||||
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
<a class="buy-link" href="https://platform.deepseek.com/" target="_blank">→ 获取 API Key</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-card" data-provider="minimax" data-base="https://api.minimax.chat/v1" data-model="MiniMax-M2">
|
<div class="model-card" data-provider="minimax" data-base="https://api.minimax.chat/v1" data-model="MiniMax-M2">
|
||||||
|
|||||||
Reference in New Issue
Block a user