fix: app update check in all modes, add Check for Updates menu, clean upgrade output
- App self-update check no longer skipped in remote mode - Added "Check for Updates" button in sidebar - Strip ANSI escape codes from upgrade output - Collapse upgrade log behind "Show details" on success - Updated Discord link and added icons to landing page buttons - Recorded deployment docs in agents.md Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
23
agents.md
23
agents.md
@@ -59,7 +59,28 @@
|
||||
- `docs/plans/2026-02-15-clawpal-mvp-design.md`(设计)
|
||||
- `docs/plans/2026-02-15-clawpal-mvp-implementation-plan.md`(计划)
|
||||
|
||||
## 7. 安全与风险
|
||||
## 7. 部署
|
||||
|
||||
### 官网(clawpal.zhixian.io)
|
||||
|
||||
使用 Cloudflare Pages Direct Upload 部署,源目录为 `docs/site/`。
|
||||
|
||||
部署命令:
|
||||
```bash
|
||||
npx wrangler pages deploy docs/site --project-name clawpal
|
||||
```
|
||||
|
||||
项目域名:`clawpal.zhixian.io`(也可通过 `clawpal.pages.dev` 访问)。
|
||||
|
||||
### 桌面应用 Release
|
||||
|
||||
通过 GitHub Actions 自动构建,push tag 触发(如 `v0.1.1`):
|
||||
- CI workflow: `.github/workflows/release.yml`
|
||||
- 构建产物:macOS (ARM/x64 .dmg)、Windows (.exe/.msi)、Linux (.deb/.AppImage)
|
||||
- 需要 `TAURI_SIGNING_PRIVATE_KEY` 等 secrets,本地无法打 release bundle
|
||||
- 发布新版本流程:更新 `package.json` + `src-tauri/Cargo.toml` 版本号 → commit → `git tag vX.Y.Z` → push
|
||||
|
||||
## 8. 安全与风险
|
||||
|
||||
- 禁止提交明文密钥/配置路径泄露
|
||||
- 避免大文件和自动生成产物直接提交
|
||||
|
||||
@@ -423,6 +423,12 @@
|
||||
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
|
||||
</symbol>
|
||||
<symbol id="icon-github" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.295 24 12 24 5.37 18.63 0 12 0"/>
|
||||
</symbol>
|
||||
<symbol id="icon-discord" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<!-- Nav -->
|
||||
@@ -449,14 +455,16 @@
|
||||
<p class="reveal reveal-delay-2">Manage AI agents, models, and configs with a visual interface.<br>Stop editing JSON by hand.</p>
|
||||
<div class="hero-actions reveal reveal-delay-3">
|
||||
<a href="#download" class="btn" style="padding:14px 32px;font-size:1rem;border-radius:12px;">
|
||||
<svg width="18" height="18"><use href="#icon-download"/></svg>
|
||||
Download
|
||||
<svg width="16" height="16"><use href="#icon-download"/></svg>
|
||||
</a>
|
||||
<a href="https://github.com/zhixianio/clawpal" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
||||
View on GitHub
|
||||
<svg width="18" height="18"><use href="#icon-github"/></svg>
|
||||
GitHub
|
||||
</a>
|
||||
<a href="https://discord.gg/4yqf28wf7R" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
||||
Join Discord
|
||||
<a href="https://discord.gg/T3nPEG9TZN" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
||||
<svg width="18" height="18"><use href="#icon-discord"/></svg>
|
||||
Discord
|
||||
</a>
|
||||
</div>
|
||||
<div class="hero-sub reveal reveal-delay-4">
|
||||
|
||||
34
src/App.tsx
34
src/App.tsx
@@ -1,4 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { check } from "@tauri-apps/plugin-updater";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { Home } from "./pages/Home";
|
||||
import { Recipes } from "./pages/Recipes";
|
||||
import { Cook } from "./pages/Cook";
|
||||
@@ -80,6 +82,30 @@ export function App() {
|
||||
setToasts((prev) => prev.filter((t) => t.id !== id));
|
||||
}, []);
|
||||
|
||||
// App self-update (manual check from sidebar)
|
||||
const [checkingAppUpdate, setCheckingAppUpdate] = useState(false);
|
||||
|
||||
const handleCheckForUpdates = useCallback(async () => {
|
||||
setCheckingAppUpdate(true);
|
||||
try {
|
||||
const update = await check();
|
||||
if (update) {
|
||||
const confirmed = window.confirm(`ClawPal v${update.version} is available. Update and restart now?`);
|
||||
if (confirmed) {
|
||||
await update.downloadAndInstall();
|
||||
await relaunch();
|
||||
}
|
||||
} else {
|
||||
showToast("You're on the latest version");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Update check failed:", e);
|
||||
showToast(`Update check failed: ${e}`, "error");
|
||||
} finally {
|
||||
setCheckingAppUpdate(false);
|
||||
}
|
||||
}, [showToast]);
|
||||
|
||||
const handleInstanceSelect = useCallback((id: string) => {
|
||||
setActiveInstance(id);
|
||||
if (id !== "local") {
|
||||
@@ -285,6 +311,14 @@ export function App() {
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="justify-start hover:bg-accent text-muted-foreground"
|
||||
onClick={handleCheckForUpdates}
|
||||
disabled={checkingAppUpdate}
|
||||
>
|
||||
{checkingAppUpdate ? "Checking..." : "Check for Updates"}
|
||||
</Button>
|
||||
</nav>
|
||||
|
||||
{/* Dirty config action bar */}
|
||||
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
|
||||
type Step = "confirm" | "backup" | "upgrading" | "done";
|
||||
|
||||
/** Strip ANSI escape codes from terminal output */
|
||||
function stripAnsi(str: string): string {
|
||||
return str.replace(/\x1b\[[0-9;]*m/g, "").replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
||||
}
|
||||
|
||||
export function UpgradeDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
@@ -31,6 +36,7 @@ export function UpgradeDialog({
|
||||
const [output, setOutput] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showLog, setShowLog] = useState(false);
|
||||
|
||||
const reset = () => {
|
||||
setStep("confirm");
|
||||
@@ -38,6 +44,7 @@ export function UpgradeDialog({
|
||||
setOutput("");
|
||||
setError("");
|
||||
setLoading(false);
|
||||
setShowLog(false);
|
||||
};
|
||||
|
||||
const handleClose = (open: boolean) => {
|
||||
@@ -77,11 +84,12 @@ export function UpgradeDialog({
|
||||
const result = isRemote
|
||||
? await api.remoteRunOpenclawUpgrade(instanceId)
|
||||
: await api.runOpenclawUpgrade();
|
||||
setOutput(result);
|
||||
setOutput(stripAnsi(result));
|
||||
setStep("done");
|
||||
} catch (e) {
|
||||
setOutput(String(e));
|
||||
setOutput(stripAnsi(String(e)));
|
||||
setError("Upgrade failed. See output below.");
|
||||
setShowLog(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -163,9 +171,19 @@ export function UpgradeDialog({
|
||||
Upgrade completed successfully.
|
||||
</p>
|
||||
{output && (
|
||||
<pre className="max-h-60 overflow-auto rounded-md bg-muted p-3 text-xs font-mono whitespace-pre-wrap">
|
||||
{output}
|
||||
</pre>
|
||||
<>
|
||||
<button
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
onClick={() => setShowLog(!showLog)}
|
||||
>
|
||||
{showLog ? "Hide details" : "Show details"}
|
||||
</button>
|
||||
{showLog && (
|
||||
<pre className="max-h-60 overflow-auto rounded-md bg-muted p-3 text-xs font-mono whitespace-pre-wrap">
|
||||
{output}
|
||||
</pre>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -201,9 +201,8 @@ export function Home({
|
||||
return () => clearTimeout(timer);
|
||||
}, [isRemote, isConnected, instanceId]);
|
||||
|
||||
// ClawPal app self-update check (local only)
|
||||
// ClawPal app self-update check
|
||||
useEffect(() => {
|
||||
if (isRemote) return;
|
||||
setAppUpdateChecking(true);
|
||||
check()
|
||||
.then((update) => {
|
||||
@@ -213,10 +212,9 @@ export function Home({
|
||||
})
|
||||
.catch((e) => console.error("Failed to check app update:", e))
|
||||
.finally(() => setAppUpdateChecking(false));
|
||||
}, [isRemote]);
|
||||
}, []);
|
||||
|
||||
const handleAppUpdate = useCallback(async () => {
|
||||
if (isRemote) return;
|
||||
setAppUpdating(true);
|
||||
setAppUpdateProgress(0);
|
||||
try {
|
||||
@@ -242,7 +240,7 @@ export function Home({
|
||||
setAppUpdating(false);
|
||||
setAppUpdateProgress(null);
|
||||
}
|
||||
}, [isRemote]);
|
||||
}, []);
|
||||
|
||||
const handleDeleteAgent = (agentId: string) => {
|
||||
if (isRemote && !isConnected) return;
|
||||
@@ -303,8 +301,8 @@ export function Home({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ClawPal app self-update (local only) */}
|
||||
{!isRemote && (appUpdateChecking || appUpdate) && (
|
||||
{/* ClawPal app self-update */}
|
||||
{(appUpdateChecking || appUpdate) && (
|
||||
<>
|
||||
<span className="text-sm text-muted-foreground">App Update</span>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
|
||||
Reference in New Issue
Block a user