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-design.md`(设计)
|
||||||
- `docs/plans/2026-02-15-clawpal-mvp-implementation-plan.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">
|
<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"/>
|
<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>
|
||||||
|
<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>
|
</svg>
|
||||||
|
|
||||||
<!-- Nav -->
|
<!-- 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>
|
<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">
|
<div class="hero-actions reveal reveal-delay-3">
|
||||||
<a href="#download" class="btn" style="padding:14px 32px;font-size:1rem;border-radius:12px;">
|
<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
|
Download
|
||||||
<svg width="16" height="16"><use href="#icon-download"/></svg>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/zhixianio/clawpal" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
<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>
|
||||||
<a href="https://discord.gg/4yqf28wf7R" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
<a href="https://discord.gg/T3nPEG9TZN" class="btn btn-outline" style="padding:14px 32px;font-size:1rem;border-radius:12px;" target="_blank">
|
||||||
Join Discord
|
<svg width="18" height="18"><use href="#icon-discord"/></svg>
|
||||||
|
Discord
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-sub reveal reveal-delay-4">
|
<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 { 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 { Home } from "./pages/Home";
|
||||||
import { Recipes } from "./pages/Recipes";
|
import { Recipes } from "./pages/Recipes";
|
||||||
import { Cook } from "./pages/Cook";
|
import { Cook } from "./pages/Cook";
|
||||||
@@ -80,6 +82,30 @@ export function App() {
|
|||||||
setToasts((prev) => prev.filter((t) => t.id !== id));
|
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) => {
|
const handleInstanceSelect = useCallback((id: string) => {
|
||||||
setActiveInstance(id);
|
setActiveInstance(id);
|
||||||
if (id !== "local") {
|
if (id !== "local") {
|
||||||
@@ -285,6 +311,14 @@ export function App() {
|
|||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="justify-start hover:bg-accent text-muted-foreground"
|
||||||
|
onClick={handleCheckForUpdates}
|
||||||
|
disabled={checkingAppUpdate}
|
||||||
|
>
|
||||||
|
{checkingAppUpdate ? "Checking..." : "Check for Updates"}
|
||||||
|
</Button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Dirty config action bar */}
|
{/* Dirty config action bar */}
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ import {
|
|||||||
|
|
||||||
type Step = "confirm" | "backup" | "upgrading" | "done";
|
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({
|
export function UpgradeDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -31,6 +36,7 @@ export function UpgradeDialog({
|
|||||||
const [output, setOutput] = useState("");
|
const [output, setOutput] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [showLog, setShowLog] = useState(false);
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
setStep("confirm");
|
setStep("confirm");
|
||||||
@@ -38,6 +44,7 @@ export function UpgradeDialog({
|
|||||||
setOutput("");
|
setOutput("");
|
||||||
setError("");
|
setError("");
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
setShowLog(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (open: boolean) => {
|
const handleClose = (open: boolean) => {
|
||||||
@@ -77,11 +84,12 @@ export function UpgradeDialog({
|
|||||||
const result = isRemote
|
const result = isRemote
|
||||||
? await api.remoteRunOpenclawUpgrade(instanceId)
|
? await api.remoteRunOpenclawUpgrade(instanceId)
|
||||||
: await api.runOpenclawUpgrade();
|
: await api.runOpenclawUpgrade();
|
||||||
setOutput(result);
|
setOutput(stripAnsi(result));
|
||||||
setStep("done");
|
setStep("done");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setOutput(String(e));
|
setOutput(stripAnsi(String(e)));
|
||||||
setError("Upgrade failed. See output below.");
|
setError("Upgrade failed. See output below.");
|
||||||
|
setShowLog(true);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -163,9 +171,19 @@ export function UpgradeDialog({
|
|||||||
Upgrade completed successfully.
|
Upgrade completed successfully.
|
||||||
</p>
|
</p>
|
||||||
{output && (
|
{output && (
|
||||||
<pre className="max-h-60 overflow-auto rounded-md bg-muted p-3 text-xs font-mono whitespace-pre-wrap">
|
<>
|
||||||
{output}
|
<button
|
||||||
</pre>
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -201,9 +201,8 @@ export function Home({
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [isRemote, isConnected, instanceId]);
|
}, [isRemote, isConnected, instanceId]);
|
||||||
|
|
||||||
// ClawPal app self-update check (local only)
|
// ClawPal app self-update check
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isRemote) return;
|
|
||||||
setAppUpdateChecking(true);
|
setAppUpdateChecking(true);
|
||||||
check()
|
check()
|
||||||
.then((update) => {
|
.then((update) => {
|
||||||
@@ -213,10 +212,9 @@ export function Home({
|
|||||||
})
|
})
|
||||||
.catch((e) => console.error("Failed to check app update:", e))
|
.catch((e) => console.error("Failed to check app update:", e))
|
||||||
.finally(() => setAppUpdateChecking(false));
|
.finally(() => setAppUpdateChecking(false));
|
||||||
}, [isRemote]);
|
}, []);
|
||||||
|
|
||||||
const handleAppUpdate = useCallback(async () => {
|
const handleAppUpdate = useCallback(async () => {
|
||||||
if (isRemote) return;
|
|
||||||
setAppUpdating(true);
|
setAppUpdating(true);
|
||||||
setAppUpdateProgress(0);
|
setAppUpdateProgress(0);
|
||||||
try {
|
try {
|
||||||
@@ -242,7 +240,7 @@ export function Home({
|
|||||||
setAppUpdating(false);
|
setAppUpdating(false);
|
||||||
setAppUpdateProgress(null);
|
setAppUpdateProgress(null);
|
||||||
}
|
}
|
||||||
}, [isRemote]);
|
}, []);
|
||||||
|
|
||||||
const handleDeleteAgent = (agentId: string) => {
|
const handleDeleteAgent = (agentId: string) => {
|
||||||
if (isRemote && !isConnected) return;
|
if (isRemote && !isConnected) return;
|
||||||
@@ -303,8 +301,8 @@ export function Home({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ClawPal app self-update (local only) */}
|
{/* ClawPal app self-update */}
|
||||||
{!isRemote && (appUpdateChecking || appUpdate) && (
|
{(appUpdateChecking || appUpdate) && (
|
||||||
<>
|
<>
|
||||||
<span className="text-sm text-muted-foreground">App Update</span>
|
<span className="text-sm text-muted-foreground">App Update</span>
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
|||||||
Reference in New Issue
Block a user