mirror of
https://github.com/dtzp555-max/ocm.git
synced 2026-07-21 21:15:08 +00:00
chore: release v0.5.1 and streamline README
This commit is contained in:
@@ -1,7 +1,41 @@
|
|||||||
# OpenClaw Manager — 开发日志
|
# OpenClaw Manager — 开发日志
|
||||||
|
|
||||||
> 最后更新:2026-02-24
|
> 最后更新:2026-02-25
|
||||||
> 当前版本:v0.5.0
|
> 当前版本:v0.5.1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v0.5.1 更新日志(2026-02-25)
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
|
||||||
|
**Action 菜单英文模式残留中文**
|
||||||
|
- 修复 Action 菜单下多处弹窗在 EN 模式仍显示中文的问题(日志、回滚、目录切换、命令输出)
|
||||||
|
- 统一接入 i18n:标题、按钮、说明文案、加载态、空态、确认框、toast 文案
|
||||||
|
- 菜单项触发后的交互文案现在与语言切换保持一致
|
||||||
|
|
||||||
|
### 工程改进
|
||||||
|
|
||||||
|
**1) 嵌入脚本转义/语法防护**
|
||||||
|
- 新增启动时预检:对前端嵌入脚本执行 `node --check` 语法校验
|
||||||
|
- 若语法异常(常见为 `MAIN_HTML` 内 `\n` 转义写错),启动阶段直接报错并给出修复提示,避免浏览器白屏后才发现问题
|
||||||
|
|
||||||
|
**2) 单文件结构化重构(保持零依赖)**
|
||||||
|
- 将主页面模板从单一超长 `MAIN_HTML` 拆分为三段常量:
|
||||||
|
- `MAIN_HTML_CSS`
|
||||||
|
- `MAIN_HTML_BODY`
|
||||||
|
- `MAIN_HTML_SCRIPT`
|
||||||
|
- 最终由 `MAIN_HTML` 组合输出,保持“单文件 + 零依赖”不变,同时降低维护复杂度
|
||||||
|
- 修复拆分过程中的脚本模板转义问题:`MAIN_HTML_SCRIPT` 使用普通模板字符串(非 `String.raw`),确保前端脚本内的反引号模板语法可被正确还原
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
- 程序头部版本与启动日志版本更新为 `v0.5.1`
|
||||||
|
- `start.sh` / `start.bat` 启动横幅版本号同步更新为 `v0.5.1`
|
||||||
|
- `README` 补充 Git 安装/更新流程(`git clone` / `git pull --ff-only`),并精简中英文功能介绍
|
||||||
|
- `README` 首屏改版:增加「1 分钟上手」命令区(安装/更新/运行同屏),并将开场介绍改为精简版
|
||||||
|
- `README` 去重:移除中部重复的安装/更新/运行段落,统一引用首屏快速上手
|
||||||
|
- `README` 截图区改为首屏精简展示(3 张)+ 折叠更多截图(`<details>`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,28 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What is this?
|
## Quick Start (1 min)
|
||||||
|
|
||||||
OCM is a lightweight local dashboard for OpenClaw that helps you:
|
```bash
|
||||||
|
# Install
|
||||||
|
git clone https://github.com/dtzp555-max/ocm.git
|
||||||
|
cd ocm
|
||||||
|
|
||||||
- **Manage sub-agents fast** (create with a wizard, switch models inline)
|
# Update (existing clone)
|
||||||
- **Run OpenClaw commands inside the app** via a built-in CLI terminal
|
git pull --ff-only
|
||||||
- **Stop hunting for CLI flags / docs** with curated command presets + favorites
|
|
||||||
|
|
||||||
Everything ships as **a single `.js` file** with zero npm dependencies — just Node.js 18+.
|
# Run (no npm install needed)
|
||||||
|
bash start.sh # macOS / Linux
|
||||||
|
start.bat # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3333](http://localhost:3333).
|
||||||
|
|
||||||
|
## Why OCM
|
||||||
|
|
||||||
|
- **Single-file + zero dependency**: one `openclaw-manager.js`, no build step
|
||||||
|
- **Fast daily operations**: agent/model/auth/cron/backup in one local UI
|
||||||
|
- **Built-in terminal**: run `openclaw` commands with streaming output and presets
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
@@ -28,6 +41,14 @@ View all agents (main + sub-agents) at a glance. Each card shows model, group bi
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
### Built-in CLI Terminal
|
||||||
|
Run any openclaw command with real-time streaming output. The terminal panel sits at the bottom of the page and expands on demand.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>More screenshots</summary>
|
||||||
|
|
||||||
### New Subagent Wizard
|
### New Subagent Wizard
|
||||||
4-step guided wizard to create a sub-agent: basic info → model → personality & memory → confirm. Fully bilingual.
|
4-step guided wizard to create a sub-agent: basic info → model → personality & memory → confirm. Fully bilingual.
|
||||||
|
|
||||||
@@ -48,11 +69,6 @@ View, add, enable/disable, and manually trigger openclaw-related cron tasks. Int
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Built-in CLI Terminal
|
|
||||||
Run any openclaw command with real-time streaming output. The terminal panel sits at the bottom of the page and expands on demand.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Preset command menu with built-in commands and your personal favorites — one click to run.
|
Preset command menu with built-in commands and your personal favorites — one click to run.
|
||||||
|
|
||||||

|

|
||||||
@@ -60,42 +76,23 @@ Preset command menu with built-in commands and your personal favorites — one c
|
|||||||
Command output streams in real-time. Star frequently used commands for quick access, or use the Manage button to organize favorites.
|
Command output streams in real-time. Star frequently used commands for quick access, or use the Manage button to organize favorites.
|
||||||
|
|
||||||

|

|
||||||
|
</details>
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Agent Management** — Create sub-agents (with wizard), switch models inline, browse & edit workspace files, delete with auto-backup
|
- **Agents & Workspace** — Create sub-agents, switch models inline, and browse/edit workspace files
|
||||||
- **Model Selection** — Change global primary model, edit fallback chain
|
- **Models & Auth** — Manage primary/fallback models and follow provider auth guides
|
||||||
- **Auth Guide** — Step-by-step instructions for configuring each provider (Anthropic, OpenAI, DeepSeek, Google, GitHub Copilot, etc.)
|
- **Ops Panel** — Restart gateway, view logs, run health checks, and switch OpenClaw directory
|
||||||
- **Token Usage Stats** — Parse `gateway.log` for token usage, view cost breakdown by model and day
|
- **Stats & Cron** — Token/cost stats from `gateway.log` plus cron task management
|
||||||
- **Cron Job Management** — View, add, enable/disable, and trigger openclaw-related cron tasks
|
- **Backups** — Local backup/rollback and NAS backup (SFTP/rsync)
|
||||||
- **Workspace File Browser** — Browse all agent workspace files, read-only by default, editable on demand
|
- **Built-in CLI** — Real-time command terminal with presets, favorites, and tab completion
|
||||||
- **Backup & Rollback** — Auto-backup before every write, one-click restore from any snapshot
|
|
||||||
- **NAS Backup** — SFTP/rsync backup to NAS with legacy SSH cipher compatibility
|
|
||||||
- **Built-in CLI Terminal** — Run openclaw commands with real-time streaming output, presets, tab completion
|
|
||||||
- **Health Badge** — Auto-run `openclaw doctor` and show warnings in the header
|
|
||||||
- **Bilingual UI** — English / 中文, switchable at runtime
|
|
||||||
- **Cross-platform** — macOS / Linux / Windows
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Node.js >= 18 ([download](https://nodejs.org/))
|
- Node.js >= 18 ([download](https://nodejs.org/))
|
||||||
- A working [OpenClaw](https://github.com/anthropics/openclaw) installation
|
- A working [OpenClaw](https://github.com/anthropics/openclaw) installation
|
||||||
|
|
||||||
## Quick Start
|
Install/update/run commands are in **Quick Start (1 min)** above.
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/dtzp555-max/ocm.git
|
|
||||||
cd ocm
|
|
||||||
# No npm install needed
|
|
||||||
|
|
||||||
# macOS / Linux
|
|
||||||
bash start.sh
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
start.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open http://localhost:3333 in your browser.
|
|
||||||
|
|
||||||
### First Run
|
### First Run
|
||||||
|
|
||||||
@@ -176,13 +173,40 @@ MIT
|
|||||||
|
|
||||||
OpenClaw Manager 是一个零依赖的本地 Web 管理界面,用于可视化管理 [OpenClaw](https://github.com/anthropics/openclaw) AI 智能体。所有代码合并在一个 `.js` 文件中,只需要 Node.js 18+,不需要 `npm install`。
|
OpenClaw Manager 是一个零依赖的本地 Web 管理界面,用于可视化管理 [OpenClaw](https://github.com/anthropics/openclaw) AI 智能体。所有代码合并在一个 `.js` 文件中,只需要 Node.js 18+,不需要 `npm install`。
|
||||||
|
|
||||||
|
### 1 分钟上手
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 首次安装
|
||||||
|
git clone https://github.com/dtzp555-max/ocm.git
|
||||||
|
cd ocm
|
||||||
|
|
||||||
|
# 已安装后更新
|
||||||
|
git pull --ff-only
|
||||||
|
|
||||||
|
# 运行(无需 npm install)
|
||||||
|
bash start.sh # macOS / Linux
|
||||||
|
start.bat # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
访问 [http://localhost:3333](http://localhost:3333)。
|
||||||
|
|
||||||
### 界面预览
|
### 界面预览
|
||||||
|
|
||||||
#### 模式选择
|
#### 模式选择
|
||||||

|

|
||||||
|
|
||||||
#### Agent 管理 & 新建向导
|
#### Agent 管理
|
||||||

|

|
||||||
|
|
||||||
|
#### 内置 CLI 终端
|
||||||
|
底部终端面板,支持实时流式输出、预设命令、收藏夹、Tab 补全。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>更多截图</summary>
|
||||||
|
|
||||||
|
#### 新建向导
|
||||||

|

|
||||||
|
|
||||||
#### 模型选择 & 操作菜单
|
#### 模型选择 & 操作菜单
|
||||||
@@ -194,42 +218,20 @@ OpenClaw Manager 是一个零依赖的本地 Web 管理界面,用于可视化
|
|||||||
#### Cron 定时任务
|
#### Cron 定时任务
|
||||||

|

|
||||||
|
|
||||||
#### 内置 CLI 终端
|
|
||||||
底部终端面板,支持实时流式输出、预设命令、收藏夹、Tab 补全。
|
|
||||||
|
|
||||||

|
|
||||||

|

|
||||||

|

|
||||||
|
</details>
|
||||||
|
|
||||||
### 功能一览
|
### 功能一览
|
||||||
|
|
||||||
- **Agent 管理** — 新建子智能体(含向导)、内联切换模型、查看/编辑工作区文件、删除(自动备份)
|
- **Agent 与 Workspace** — 新建子智能体、内联切换模型、浏览/编辑工作区文件
|
||||||
- **模型选择** — 修改全局主模型、编辑 Fallback 链
|
- **模型与认证** — 管理主模型/Fallback 链,按引导完成各 Provider 认证
|
||||||
- **认证引导** — 各 Provider 分步操作指引 + 一键复制 CLI 命令
|
- **运维操作面板** — 重启网关、查看日志、健康检查、切换 OpenClaw 目录
|
||||||
- **Token 用量统计** — 解析网关日志,按模型/天汇总 Token 和费用
|
- **统计与计划任务** — 从 `gateway.log` 汇总 Token/费用,并管理 Cron 任务
|
||||||
- **Cron 任务管理** — 查看、添加、启用/禁用、手动触发 openclaw 相关定时任务
|
- **备份能力** — 本地备份回滚 + NAS 远程备份(SFTP/rsync)
|
||||||
- **Workspace 文件浏览器** — 查看所有工作区文件,默认只读,可切换编辑
|
- **内置 CLI 终端** — 实时输出、预设命令、收藏、Tab 补全
|
||||||
- **备份 & 回滚** — 每次写入前自动备份,一键回滚
|
|
||||||
- **NAS 备份** — SFTP/rsync 远程备份,兼容老设备 SSH 加密
|
|
||||||
- **内置 CLI 终端** — 实时流式输出、预设命令、Tab 补全
|
|
||||||
- **健康检查** — 自动运行 `openclaw doctor`,Header 显示状态徽章
|
|
||||||
- **中英双语** — 运行时随时切换
|
|
||||||
- **跨平台** — macOS / Linux / Windows
|
|
||||||
|
|
||||||
### 快速开始
|
安装/更新/运行命令见上方 **1 分钟上手**。
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/dtzp555-max/ocm.git
|
|
||||||
cd ocm
|
|
||||||
|
|
||||||
# macOS / Linux
|
|
||||||
bash start.sh
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
start.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
访问 http://localhost:3333
|
|
||||||
|
|
||||||
### 首次运行
|
### 首次运行
|
||||||
|
|
||||||
|
|||||||
+110
-60
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// OpenClaw Manager v0.5.0
|
// OpenClaw Manager v0.5.1
|
||||||
// 跨平台本地管理工具 (Windows / macOS / Linux)
|
// 跨平台本地管理工具 (Windows / macOS / Linux)
|
||||||
//
|
//
|
||||||
// 用法:
|
// 用法:
|
||||||
@@ -22,6 +22,7 @@ const { exec, execSync, spawn, spawnSync } = require('child_process');
|
|||||||
const SCRIPT_DIR = __dirname;
|
const SCRIPT_DIR = __dirname;
|
||||||
const MANAGER_CONFIG = path.join(SCRIPT_DIR, 'manager-config.json');
|
const MANAGER_CONFIG = path.join(SCRIPT_DIR, 'manager-config.json');
|
||||||
let PORT = 3333;
|
let PORT = 3333;
|
||||||
|
const APP_VERSION = '0.5.1';
|
||||||
// --port 参数
|
// --port 参数
|
||||||
const portIdx = process.argv.indexOf('--port');
|
const portIdx = process.argv.indexOf('--port');
|
||||||
if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333;
|
if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333;
|
||||||
@@ -1180,14 +1181,7 @@ document.getElementById('dir').addEventListener('keydown',e=>{if(e.key==='Enter'
|
|||||||
</script></body></html>`;
|
</script></body></html>`;
|
||||||
|
|
||||||
// ── 主 HTML 前端 ──────────────────────────────────────────────
|
// ── 主 HTML 前端 ──────────────────────────────────────────────
|
||||||
const MAIN_HTML = `<!DOCTYPE html>
|
const MAIN_HTML_CSS = String.raw`*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>OpenClaw Manager</title>
|
|
||||||
<style>
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #0f1117; --surface: #1a1d27; --border: #2d3148;
|
--bg: #0f1117; --surface: #1a1d27; --border: #2d3148;
|
||||||
--accent: #6c63ff; --accent-h: #7c74ff;
|
--accent: #6c63ff; --accent-h: #7c74ff;
|
||||||
@@ -1400,11 +1394,9 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
|
|||||||
/* Manage favs modal list */
|
/* Manage favs modal list */
|
||||||
.fav-manage-row { display:flex; align-items:center; gap:6px; padding:7px 0; border-bottom:1px solid var(--border); }
|
.fav-manage-row { display:flex; align-items:center; gap:6px; padding:7px 0; border-bottom:1px solid var(--border); }
|
||||||
.fav-manage-label { flex:1; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.fav-manage-label { flex:1; font-size:13px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||||
.fav-manage-cmd { font-size:11px; color:var(--muted); font-family:monospace; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
.fav-manage-cmd { font-size:11px; color:var(--muted); font-family:monospace; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }`;
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
|
const MAIN_HTML_BODY = String.raw`
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
<!-- LANDING PAGE -->
|
<!-- LANDING PAGE -->
|
||||||
<!-- ═══════════════════════════════════════════════════════════ -->
|
<!-- ═══════════════════════════════════════════════════════════ -->
|
||||||
@@ -1791,29 +1783,29 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
|
|||||||
<!-- ══ 日志查看 ══════════════════════════════════════════════ -->
|
<!-- ══ 日志查看 ══════════════════════════════════════════════ -->
|
||||||
<div class="backdrop" id="logModal">
|
<div class="backdrop" id="logModal">
|
||||||
<div class="modal wide">
|
<div class="modal wide">
|
||||||
<div class="m-hdr"><h3>📋 Gateway 日志</h3><button class="m-close" onclick="closeLogs()">✕</button></div>
|
<div class="m-hdr"><h3 data-i18n="actions.logsTitle">📋 Gateway 日志</h3><button class="m-close" onclick="closeLogs()">✕</button></div>
|
||||||
<div class="m-body">
|
<div class="m-body">
|
||||||
<div style="display:flex;gap:8px;margin-bottom:10px;align-items:center">
|
<div style="display:flex;gap:8px;margin-bottom:10px;align-items:center">
|
||||||
<button class="btn-secondary" onclick="refreshLogs()">🔄 刷新</button>
|
<button class="btn-secondary" onclick="refreshLogs()" data-i18n="actions.refresh">🔄 刷新</button>
|
||||||
<label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer">
|
<label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer">
|
||||||
<input type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()" style="width:auto"> 自动刷新(2s)
|
<input type="checkbox" id="autoRefresh" onchange="toggleAutoRefresh()" style="width:auto"> <span data-i18n="actions.autoRefresh">自动刷新(2s)</span>
|
||||||
</label>
|
</label>
|
||||||
<span style="margin-left:auto;font-size:11px;color:var(--muted)" id="logPath"></span>
|
<span style="margin-left:auto;font-size:11px;color:var(--muted)" id="logPath"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="log-box" id="logContent">加载中...</div>
|
<div class="log-box" id="logContent" data-i18n="common.loading">加载中...</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-foot"><button class="btn-secondary" onclick="closeLogs()">关闭</button></div>
|
<div class="m-foot"><button class="btn-secondary" onclick="closeLogs()" data-i18n="common.close">关闭</button></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<!-- ══ 回滚 ════════════════════════════════════════════════ -->
|
<!-- ══ 回滚 ════════════════════════════════════════════════ -->
|
||||||
<div class="backdrop" id="rollbackModal">
|
<div class="backdrop" id="rollbackModal">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="m-hdr"><h3>📦 备份 / 回滚</h3><button class="m-close" onclick="closeModal('rollbackModal')">✕</button></div>
|
<div class="m-hdr"><h3 data-i18n="actions.rollbackTitle">📦 备份 / 回滚</h3><button class="m-close" onclick="closeModal('rollbackModal')">✕</button></div>
|
||||||
<div class="m-body">
|
<div class="m-body">
|
||||||
<p class="hint-text" style="margin-bottom:12px">选择一个备份文件恢复。恢复前会自动保存当前配置。</p>
|
<p class="hint-text" style="margin-bottom:12px" data-i18n="actions.rollbackHint">选择一个备份文件恢复。恢复前会自动保存当前配置。</p>
|
||||||
<div class="card-grid" id="backupList"><div class="empty">加载中...</div></div>
|
<div class="card-grid" id="backupList"><div class="empty" data-i18n="common.loading">加载中...</div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('rollbackModal')">关闭</button></div>
|
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('rollbackModal')" data-i18n="common.close">关闭</button></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<!-- ══ NAS 备份设置 ════════════════════════════════════════ -->
|
<!-- ══ NAS 备份设置 ════════════════════════════════════════ -->
|
||||||
@@ -1898,27 +1890,27 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
|
|||||||
<!-- ══ 目录设置 ════════════════════════════════════════════ -->
|
<!-- ══ 目录设置 ════════════════════════════════════════════ -->
|
||||||
<div class="backdrop" id="setupModal">
|
<div class="backdrop" id="setupModal">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="m-hdr"><h3>⚙️ 切换 OpenClaw 目录</h3><button class="m-close" onclick="closeModal('setupModal')">✕</button></div>
|
<div class="m-hdr"><h3 data-i18n="actions.setupTitle">⚙️ 切换 OpenClaw 目录</h3><button class="m-close" onclick="closeModal('setupModal')">✕</button></div>
|
||||||
<div class="m-body">
|
<div class="m-body">
|
||||||
<div class="form-group"><label>OpenClaw 数据目录路径</label>
|
<div class="form-group"><label data-i18n="actions.setupLabel">OpenClaw 数据目录路径</label>
|
||||||
<input id="setup-dir" placeholder="~/.openclaw 或绝对路径"></div>
|
<input id="setup-dir" data-i18n-placeholder="actions.setupPlaceholder" placeholder="~/.openclaw 或绝对路径"></div>
|
||||||
<div class="field-err" id="setup-err"></div>
|
<div class="field-err" id="setup-err"></div>
|
||||||
<p class="hint-text" style="margin-top:6px">需要包含 openclaw.json 的文件夹。更换后页面自动刷新。</p>
|
<p class="hint-text" style="margin-top:6px" data-i18n="actions.setupHint">需要包含 openclaw.json 的文件夹。更换后页面自动刷新。</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-foot">
|
<div class="m-foot">
|
||||||
<button class="btn-secondary" onclick="closeModal('setupModal')">取消</button>
|
<button class="btn-secondary" onclick="closeModal('setupModal')" data-i18n="btn.cancel">取消</button>
|
||||||
<button class="btn-primary" onclick="submitSetup()">确认切换</button>
|
<button class="btn-primary" onclick="submitSetup()" data-i18n="actions.setupConfirm">确认切换</button>
|
||||||
</div>
|
</div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<!-- ══ 命令输出 ════════════════════════════════════════════ -->
|
<!-- ══ 命令输出 ════════════════════════════════════════════ -->
|
||||||
<div class="backdrop" id="cmdModal">
|
<div class="backdrop" id="cmdModal">
|
||||||
<div class="modal">
|
<div class="modal">
|
||||||
<div class="m-hdr"><h3 id="cmdTitle">输出</h3><button class="m-close" onclick="closeModal('cmdModal')">✕</button></div>
|
<div class="m-hdr"><h3 id="cmdTitle" data-i18n="actions.outputTitle">输出</h3><button class="m-close" onclick="closeModal('cmdModal')">✕</button></div>
|
||||||
<div class="m-body">
|
<div class="m-body">
|
||||||
<div class="log-box" id="cmdOutput" style="max-height:260px">...</div>
|
<div class="log-box" id="cmdOutput" style="max-height:260px">...</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('cmdModal')">关闭</button></div>
|
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('cmdModal')" data-i18n="common.close">关闭</button></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<!-- Workspace 文件浏览器 -->
|
<!-- Workspace 文件浏览器 -->
|
||||||
@@ -1926,14 +1918,13 @@ code { font-size:12px; background:rgba(0,0,0,.3); padding:2px 6px; border-radius
|
|||||||
<div class="modal wide" style="max-width:780px">
|
<div class="modal wide" style="max-width:780px">
|
||||||
<div class="m-hdr"><h3 data-i18n="ws.title">📂 Workspace 文件</h3><button class="m-close" onclick="closeModal('wsModal')">✕</button></div>
|
<div class="m-hdr"><h3 data-i18n="ws.title">📂 Workspace 文件</h3><button class="m-close" onclick="closeModal('wsModal')">✕</button></div>
|
||||||
<div class="m-body" id="wsContent" style="max-height:70vh;overflow-y:auto"></div>
|
<div class="m-body" id="wsContent" style="max-height:70vh;overflow-y:auto"></div>
|
||||||
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('wsModal')" data-i18n="btn.cancel">关闭</button></div>
|
<div class="m-foot"><button class="btn-secondary" onclick="closeModal('wsModal')" data-i18n="common.close">关闭</button></div>
|
||||||
</div></div>
|
</div></div>
|
||||||
|
|
||||||
<!-- Toast -->
|
<!-- Toast -->
|
||||||
<div id="toast" style="position:fixed;bottom:20px;right:20px;z-index:999;display:flex;flex-direction:column;gap:8px"></div>
|
<div id="toast" style="position:fixed;bottom:20px;right:20px;z-index:999;display:flex;flex-direction:column;gap:8px"></div>`;
|
||||||
|
|
||||||
<script>
|
const MAIN_HTML_SCRIPT = `// ── i18n ──────────────────────────────────────────────────────
|
||||||
// ── i18n ──────────────────────────────────────────────────────
|
|
||||||
const I18N = {
|
const I18N = {
|
||||||
zh: {
|
zh: {
|
||||||
'tab.agents':'🤖 Agents','tab.channels':'📡 Channels','tab.models':'🧠 模型','tab.auth':'🔑 认证',
|
'tab.agents':'🤖 Agents','tab.channels':'📡 Channels','tab.models':'🧠 模型','tab.auth':'🔑 认证',
|
||||||
@@ -1955,6 +1946,20 @@ const I18N = {
|
|||||||
'menu.ops':'操作','menu.restart':'重启 Gateway','menu.logs':'实时日志','menu.backup':'手动备份配置',
|
'menu.ops':'操作','menu.restart':'重启 Gateway','menu.logs':'实时日志','menu.backup':'手动备份配置',
|
||||||
'menu.rollback':'查看备份 / 回滚','menu.nas':'NAS 备份设置','menu.doctor':'健康检查',
|
'menu.rollback':'查看备份 / 回滚','menu.nas':'NAS 备份设置','menu.doctor':'健康检查',
|
||||||
'menu.opendir':'打开配置目录','menu.switchdir':'切换 OpenClaw 目录',
|
'menu.opendir':'打开配置目录','menu.switchdir':'切换 OpenClaw 目录',
|
||||||
|
'actions.logsTitle':'📋 Gateway 日志','actions.refresh':'🔄 刷新','actions.autoRefresh':'自动刷新(2s)',
|
||||||
|
'actions.outputTitle':'输出','actions.rollbackTitle':'📦 备份 / 回滚','actions.rollbackHint':'选择一个备份文件恢复。恢复前会自动保存当前配置。',
|
||||||
|
'actions.restoreThis':'⏪ 恢复此备份','actions.setupTitle':'⚙️ 切换 OpenClaw 目录','actions.setupLabel':'OpenClaw 数据目录路径',
|
||||||
|
'actions.setupPlaceholder':'~/.openclaw 或绝对路径','actions.setupHint':'需要包含 openclaw.json 的文件夹。更换后页面自动刷新。','actions.setupConfirm':'确认切换',
|
||||||
|
'actions.restartTitle':'重启 Gateway','actions.doctorTitle':'健康检查 (doctor)','actions.running':'执行中...','actions.noOutput':'(无输出)',
|
||||||
|
'actions.restartSent':'重启命令已发送。','actions.restartOk':'Gateway 已重启','actions.restartFail':'重启失败: ',
|
||||||
|
'actions.restartManualHint':'请在终端手动运行:\\nopenclaw gateway restart',
|
||||||
|
'actions.backupSaved':'备份已保存: ','actions.backupFail':'备份失败: ',
|
||||||
|
'actions.logEmpty':'(空)','actions.logLoadFail':'加载失败: ',
|
||||||
|
'actions.noBackups':'暂无备份文件','actions.loadFailed':'加载失败',
|
||||||
|
'actions.restoreConfirm':'确认将配置恢复为:\\n{filename}\\n\\n当前配置将先被自动备份。继续?',
|
||||||
|
'actions.restored':'已恢复 ','actions.restoreFail':'恢复失败: ',
|
||||||
|
'actions.setupEmpty':'请填写路径','actions.setupSwitching':'目录已切换,正在刷新...','actions.setupInvalid':'路径无效','actions.setupReqFail':'请求失败: ',
|
||||||
|
'common.loading':'加载中...','common.close':'关闭',
|
||||||
'btn.save':'保存','btn.delete':'删除','btn.cancel':'取消','btn.add':'添加','btn.remove':'移除',
|
'btn.save':'保存','btn.delete':'删除','btn.cancel':'取消','btn.add':'添加','btn.remove':'移除',
|
||||||
'agents.empty':'暂无 Agent','agents.main':'主 Agent','agents.bound':'已绑群',
|
'agents.empty':'暂无 Agent','agents.main':'主 Agent','agents.bound':'已绑群',
|
||||||
'agents.saveModel':'保存模型','agents.viewFiles':'查看文件',
|
'agents.saveModel':'保存模型','agents.viewFiles':'查看文件',
|
||||||
@@ -2034,6 +2039,20 @@ const I18N = {
|
|||||||
'menu.ops':'Actions','menu.restart':'Restart Gateway','menu.logs':'Live Logs','menu.backup':'Manual Backup',
|
'menu.ops':'Actions','menu.restart':'Restart Gateway','menu.logs':'Live Logs','menu.backup':'Manual Backup',
|
||||||
'menu.rollback':'Backups / Rollback','menu.nas':'NAS Backup Setup','menu.doctor':'Health Check',
|
'menu.rollback':'Backups / Rollback','menu.nas':'NAS Backup Setup','menu.doctor':'Health Check',
|
||||||
'menu.opendir':'Open Config Dir','menu.switchdir':'Switch OpenClaw Dir',
|
'menu.opendir':'Open Config Dir','menu.switchdir':'Switch OpenClaw Dir',
|
||||||
|
'actions.logsTitle':'📋 Gateway Logs','actions.refresh':'🔄 Refresh','actions.autoRefresh':'Auto refresh (2s)',
|
||||||
|
'actions.outputTitle':'Output','actions.rollbackTitle':'📦 Backups / Rollback','actions.rollbackHint':'Select a backup file to restore. Current config will be auto-backed up first.',
|
||||||
|
'actions.restoreThis':'⏪ Restore this backup','actions.setupTitle':'⚙️ Switch OpenClaw Dir','actions.setupLabel':'OpenClaw data directory path',
|
||||||
|
'actions.setupPlaceholder':'~/.openclaw or absolute path','actions.setupHint':'Folder must contain openclaw.json. Page auto-refreshes after switching.','actions.setupConfirm':'Confirm Switch',
|
||||||
|
'actions.restartTitle':'Restart Gateway','actions.doctorTitle':'Health Check (doctor)','actions.running':'Running...','actions.noOutput':'(no output)',
|
||||||
|
'actions.restartSent':'Restart command sent.','actions.restartOk':'Gateway restarted','actions.restartFail':'Restart failed: ',
|
||||||
|
'actions.restartManualHint':'Please run manually in terminal:\\nopenclaw gateway restart',
|
||||||
|
'actions.backupSaved':'Backup saved: ','actions.backupFail':'Backup failed: ',
|
||||||
|
'actions.logEmpty':'(empty)','actions.logLoadFail':'Load failed: ',
|
||||||
|
'actions.noBackups':'No backup files','actions.loadFailed':'Load failed',
|
||||||
|
'actions.restoreConfirm':'Restore config to:\\n{filename}\\n\\nCurrent config will be auto-backed up first. Continue?',
|
||||||
|
'actions.restored':'Restored ','actions.restoreFail':'Restore failed: ',
|
||||||
|
'actions.setupEmpty':'Please enter a path','actions.setupSwitching':'Directory switched, refreshing...','actions.setupInvalid':'Invalid path','actions.setupReqFail':'Request failed: ',
|
||||||
|
'common.loading':'Loading...','common.close':'Close',
|
||||||
'btn.save':'Save','btn.delete':'Delete','btn.cancel':'Cancel','btn.add':'Add','btn.remove':'Remove',
|
'btn.save':'Save','btn.delete':'Delete','btn.cancel':'Cancel','btn.add':'Add','btn.remove':'Remove',
|
||||||
'agents.empty':'No Agents','agents.main':'Main Agent','agents.bound':'Bound',
|
'agents.empty':'No Agents','agents.main':'Main Agent','agents.bound':'Bound',
|
||||||
'agents.saveModel':'Save Model','agents.viewFiles':'View Files',
|
'agents.saveModel':'Save Model','agents.viewFiles':'View Files',
|
||||||
@@ -3155,30 +3174,30 @@ document.addEventListener('click',e=>{
|
|||||||
|
|
||||||
async function doRestart(){
|
async function doRestart(){
|
||||||
closeMenu(); dismissBanner(); hidePendingRestart();
|
closeMenu(); dismissBanner(); hidePendingRestart();
|
||||||
document.getElementById('cmdTitle').textContent='重启 Gateway';
|
document.getElementById('cmdTitle').textContent=t('actions.restartTitle');
|
||||||
document.getElementById('cmdOutput').textContent='执行中...'; openModal('cmdModal');
|
document.getElementById('cmdOutput').textContent=t('actions.running'); openModal('cmdModal');
|
||||||
try{
|
try{
|
||||||
const r=await api('POST','/api/gateway/restart');
|
const r=await api('POST','/api/gateway/restart');
|
||||||
document.getElementById('cmdOutput').textContent=r.output||'重启命令已发送。';
|
document.getElementById('cmdOutput').textContent=r.output||t('actions.restartSent');
|
||||||
toast('Gateway 已重启','success');
|
toast(t('actions.restartOk'),'success');
|
||||||
}catch(e){
|
}catch(e){
|
||||||
document.getElementById('cmdOutput').textContent='❌ '+e.message+'\\n\\n请在终端手动运行:\\nopenclaw gateway restart';
|
document.getElementById('cmdOutput').textContent='❌ '+e.message+'\\n\\n'+t('actions.restartManualHint');
|
||||||
toast('重启失败: '+e.message,'error');
|
toast(t('actions.restartFail')+e.message,'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function doDoctor(){
|
async function doDoctor(){
|
||||||
closeMenu();
|
closeMenu();
|
||||||
document.getElementById('cmdTitle').textContent='健康检查 (doctor)';
|
document.getElementById('cmdTitle').textContent=t('actions.doctorTitle');
|
||||||
document.getElementById('cmdOutput').textContent='运行中...'; openModal('cmdModal');
|
document.getElementById('cmdOutput').textContent=t('actions.running'); openModal('cmdModal');
|
||||||
try{
|
try{
|
||||||
const r=await api('POST','/api/gateway/doctor');
|
const r=await api('POST','/api/gateway/doctor');
|
||||||
document.getElementById('cmdOutput').textContent=r.output||'(无输出)';
|
document.getElementById('cmdOutput').textContent=r.output||t('actions.noOutput');
|
||||||
}catch(e){document.getElementById('cmdOutput').textContent='❌ '+e.message;}
|
}catch(e){document.getElementById('cmdOutput').textContent='❌ '+e.message;}
|
||||||
}
|
}
|
||||||
async function manualBackup(){
|
async function manualBackup(){
|
||||||
closeMenu();
|
closeMenu();
|
||||||
try{const r=await api('POST','/api/config/backup'); toast('备份已保存: '+r.bakPath.split('/').pop(),'success');}
|
try{const r=await api('POST','/api/config/backup'); toast(t('actions.backupSaved')+r.bakPath.split('/').pop(),'success');}
|
||||||
catch(e){toast('备份失败: '+e.message,'error');}
|
catch(e){toast(t('actions.backupFail')+e.message,'error');}
|
||||||
}
|
}
|
||||||
function openConfigDir(){ closeMenu(); api('POST','/api/folder/open').catch(()=>{}); }
|
function openConfigDir(){ closeMenu(); api('POST','/api/folder/open').catch(()=>{}); }
|
||||||
function closeMenu(){ document.getElementById('mainMenu').classList.remove('open'); }
|
function closeMenu(){ document.getElementById('mainMenu').classList.remove('open'); }
|
||||||
@@ -3188,10 +3207,10 @@ async function openLogs(){ closeMenu(); openModal('logModal'); await refreshLogs
|
|||||||
async function refreshLogs(){
|
async function refreshLogs(){
|
||||||
try{
|
try{
|
||||||
const r=await api('GET','/api/logs?n=300');
|
const r=await api('GET','/api/logs?n=300');
|
||||||
document.getElementById('logContent').textContent=r.content||'(空)';
|
document.getElementById('logContent').textContent=r.content||t('actions.logEmpty');
|
||||||
document.getElementById('logPath').textContent=r.path||'';
|
document.getElementById('logPath').textContent=r.path||'';
|
||||||
const lb=document.getElementById('logContent'); lb.scrollTop=lb.scrollHeight;
|
const lb=document.getElementById('logContent'); lb.scrollTop=lb.scrollHeight;
|
||||||
}catch(e){document.getElementById('logContent').textContent='加载失败: '+e.message;}
|
}catch(e){document.getElementById('logContent').textContent=t('actions.logLoadFail')+e.message;}
|
||||||
}
|
}
|
||||||
function toggleAutoRefresh(){
|
function toggleAutoRefresh(){
|
||||||
const cb=document.getElementById('autoRefresh');
|
const cb=document.getElementById('autoRefresh');
|
||||||
@@ -3204,23 +3223,24 @@ function closeLogs(){ clearInterval(logTimer); logTimer=null; document.getElemen
|
|||||||
async function openRollback(){
|
async function openRollback(){
|
||||||
closeMenu(); openModal('rollbackModal');
|
closeMenu(); openModal('rollbackModal');
|
||||||
const el=document.getElementById('backupList');
|
const el=document.getElementById('backupList');
|
||||||
el.innerHTML='<div class="empty">加载中...</div>';
|
el.innerHTML='<div class="empty">'+t('common.loading')+'</div>';
|
||||||
try{
|
try{
|
||||||
const r=await api('GET','/api/backups');
|
const r=await api('GET','/api/backups');
|
||||||
if(!r.backups.length){el.innerHTML='<div class="empty">暂无备份文件</div>';return;}
|
if(!r.backups.length){el.innerHTML='<div class="empty">'+t('actions.noBackups')+'</div>';return;}
|
||||||
el.innerHTML=r.backups.map(f=>\`<div class="card">
|
el.innerHTML=r.backups.map(f=>\`<div class="card">
|
||||||
<div class="card-row"><span class="card-title" style="font-size:13px">\${esc(f)}</span></div>
|
<div class="card-row"><span class="card-title" style="font-size:13px">\${esc(f)}</span></div>
|
||||||
<div class="card-actions"><button class="btn-warn" onclick="doRestore('\${esc(f)}')">⏪ 恢复此备份</button></div>
|
<div class="card-actions"><button class="btn-warn" onclick="doRestore('\${esc(f)}')">\${t('actions.restoreThis')}</button></div>
|
||||||
</div>\`).join('');
|
</div>\`).join('');
|
||||||
}catch(e){el.innerHTML='<div class="empty">加载失败</div>';}
|
}catch(e){el.innerHTML='<div class="empty">'+t('actions.loadFailed')+'</div>';}
|
||||||
}
|
}
|
||||||
async function doRestore(filename){
|
async function doRestore(filename){
|
||||||
if(!confirm(\`确认将配置恢复为:\\n\${filename}\\n\\n当前配置将先被自动备份。继续?\`))return;
|
const confirmMsg=t('actions.restoreConfirm').replace('{filename}',filename);
|
||||||
|
if(!confirm(confirmMsg))return;
|
||||||
try{
|
try{
|
||||||
await api('POST','/api/backups/restore',{filename});
|
await api('POST','/api/backups/restore',{filename});
|
||||||
toast('已恢复 '+filename,'success');
|
toast(t('actions.restored')+filename,'success');
|
||||||
closeModal('rollbackModal'); await loadAll(); showRestartBanner();
|
closeModal('rollbackModal'); await loadAll(); showRestartBanner();
|
||||||
}catch(e){toast('恢复失败: '+e.message,'error');}
|
}catch(e){toast(t('actions.restoreFail')+e.message,'error');}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── NAS 备份 ─────────────────────────────────────────────────
|
// ── NAS 备份 ─────────────────────────────────────────────────
|
||||||
@@ -3322,12 +3342,12 @@ function openSetupModal(){ closeMenu(); openModal('setupModal'); }
|
|||||||
async function submitSetup(){
|
async function submitSetup(){
|
||||||
const dir=document.getElementById('setup-dir').value.trim();
|
const dir=document.getElementById('setup-dir').value.trim();
|
||||||
const err=document.getElementById('setup-err');
|
const err=document.getElementById('setup-err');
|
||||||
if(!dir){err.textContent='请填写路径';return;}
|
if(!dir){err.textContent=t('actions.setupEmpty');return;}
|
||||||
try{
|
try{
|
||||||
const r=await api('POST','/api/setup',{dir});
|
const r=await api('POST','/api/setup',{dir});
|
||||||
if(r.ok){ toast('目录已切换,正在刷新...','success'); setTimeout(()=>location.reload(),800); }
|
if(r.ok){ toast(t('actions.setupSwitching'),'success'); setTimeout(()=>location.reload(),800); }
|
||||||
else err.textContent=r.error||'路径无效';
|
else err.textContent=r.error||t('actions.setupInvalid');
|
||||||
}catch(e){err.textContent='请求失败: '+e.message;}
|
}catch(e){err.textContent=t('actions.setupReqFail')+e.message;}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 重启横幅 ─────────────────────────────────────────────────
|
// ── 重启横幅 ─────────────────────────────────────────────────
|
||||||
@@ -3384,10 +3404,40 @@ document.querySelectorAll('.tab').forEach(t=>{
|
|||||||
if(t.dataset.tab==='stats') loadStats();
|
if(t.dataset.tab==='stats') loadStats();
|
||||||
if(t.dataset.tab==='cron') loadCrons();
|
if(t.dataset.tab==='cron') loadCrons();
|
||||||
});
|
});
|
||||||
});
|
});`;
|
||||||
|
|
||||||
|
const MAIN_HTML = `<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>OpenClaw Manager</title>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
${MAIN_HTML_CSS}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${MAIN_HTML_BODY}
|
||||||
|
<script>
|
||||||
|
${MAIN_HTML_SCRIPT}
|
||||||
</script>
|
</script>
|
||||||
</body></html>`;
|
</body></html>`;
|
||||||
|
|
||||||
|
function assertBrowserScriptSyntax(name, scriptText) {
|
||||||
|
const tmpFile = path.join(os.tmpdir(), `ocm-${name}-${process.pid}.js`);
|
||||||
|
fs.writeFileSync(tmpFile, scriptText, 'utf8');
|
||||||
|
const r = spawnSync(process.execPath, ['--check', tmpFile], { encoding: 'utf8', timeout: 5000 });
|
||||||
|
try { fs.unlinkSync(tmpFile); } catch (_) {}
|
||||||
|
if (r.status !== 0) {
|
||||||
|
const detail = (r.stderr || r.stdout || 'unknown syntax error').trim();
|
||||||
|
throw new Error(
|
||||||
|
`${name} syntax check failed.\n${detail}\nHint: in MAIN_HTML_SCRIPT strings, write "\\\\n" instead of "\\n".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertBrowserScriptSyntax('main-html-script', MAIN_HTML_SCRIPT);
|
||||||
|
|
||||||
// ── HTTP 服务器 ───────────────────────────────────────────────
|
// ── HTTP 服务器 ───────────────────────────────────────────────
|
||||||
const server = http.createServer(async (req, res) => {
|
const server = http.createServer(async (req, res) => {
|
||||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
@@ -3418,7 +3468,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
server.listen(PORT, '127.0.0.1', () => {
|
server.listen(PORT, '127.0.0.1', () => {
|
||||||
const url = `http://localhost:${PORT}`;
|
const url = `http://localhost:${PORT}`;
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('🦀 OpenClaw Manager v0.3');
|
console.log(`🦀 OpenClaw Manager v${APP_VERSION}`);
|
||||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||||
console.log('📂 目录:' + OPENCLAW_DIR);
|
console.log('📂 目录:' + OPENCLAW_DIR);
|
||||||
console.log('🌐 地址:' + url);
|
console.log('🌐 地址:' + url);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
@echo off
|
@echo off
|
||||||
:: ================================================================
|
:: ================================================================
|
||||||
:: OpenClaw Manager v0.5 — Windows 启动脚本
|
:: OpenClaw Manager v0.5.1 — Windows 启动脚本
|
||||||
::
|
::
|
||||||
:: 特性:
|
:: 特性:
|
||||||
:: * 自动检测 Node.js,未安装时给出安装指引(winget/scoop/官网)
|
:: * 自动检测 Node.js,未安装时给出安装指引(winget/scoop/官网)
|
||||||
@@ -19,7 +19,7 @@ set "MIN_NODE_MAJOR=18"
|
|||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ===================================
|
echo ===================================
|
||||||
echo OpenClaw Manager v0.5
|
echo OpenClaw Manager v0.5.1
|
||||||
echo -----------------------------------
|
echo -----------------------------------
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# OpenClaw Manager v0.5 — 跨平台启动脚本 (macOS / Linux)
|
# OpenClaw Manager v0.5.1 — 跨平台启动脚本 (macOS / Linux)
|
||||||
#
|
#
|
||||||
# 特性:
|
# 特性:
|
||||||
# • 自动检测 Node.js,未安装时给出安装指引
|
# • 自动检测 Node.js,未安装时给出安装指引
|
||||||
@@ -22,7 +22,7 @@ CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m'
|
|||||||
|
|
||||||
banner() {
|
banner() {
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${CYAN}${BOLD} 🦀 OpenClaw Manager v0.5${RESET}"
|
echo -e "${CYAN}${BOLD} 🦀 OpenClaw Manager v0.5.1${RESET}"
|
||||||
echo -e "${DIM} ─────────────────────────────${RESET}"
|
echo -e "${DIM} ─────────────────────────────${RESET}"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user