From 8ef619ebdd96b55005be9c8294d466bf4352fa4a Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Tue, 24 Feb 2026 20:01:15 +1000 Subject: [PATCH] init --- .gitignore | 30 + DEVLOG.md | 483 ++++++ README.md | 203 +++ openclaw-manager.command | 54 + openclaw-manager.js | 3393 ++++++++++++++++++++++++++++++++++++++ package.json | 24 + start.bat | 170 ++ start.sh | 160 ++ 8 files changed, 4517 insertions(+) create mode 100644 .gitignore create mode 100644 DEVLOG.md create mode 100644 README.md create mode 100755 openclaw-manager.command create mode 100644 openclaw-manager.js create mode 100644 package.json create mode 100644 start.bat create mode 100755 start.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f3b121 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Local configuration (machine-specific paths, do not commit) +manager-config.json + +# OpenClaw config backups +*.bak +*.bak.* +*.v*.bak.js + +# Temporary / build +/tmp/ +*.tar.gz + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux +*~ + +# Node (just in case) +node_modules/ +npm-debug.log* +yarn-error.log* +.env.local diff --git a/DEVLOG.md b/DEVLOG.md new file mode 100644 index 0000000..fafab02 --- /dev/null +++ b/DEVLOG.md @@ -0,0 +1,483 @@ +# OpenClaw Manager — 开发日志 + +> 最后更新:2026-02-24 +> 当前版本:v0.5.0 + +--- + +## v0.5 更新日志(2026-02-24) + +### 新功能 + +**认证页简化为引导模式** +- 移除了不稳定的 API Key 提交表单(通过 stdin 管道传 CLI 的方案不可靠) +- 改为引导页:点击 Provider 显示清晰的操作步骤 + 一键复制 CLI 命令 +- 已配置的 Provider 在网格中显示 ✅ 标记 +- 每个 Provider 指引末尾都提示运行 `openclaw onboard` 注册可用模型 +- 删除了 `runWithStdin()` 函数和 `POST /api/auth/run` 端点 + +**模型页精简** +- 移除「添加模型」和「删除模型」功能(模型应由 `openclaw onboard` 管理) +- 只保留 Primary Model 选择器和 Fallback Chain 编辑 +- 删除了 `POST /api/models` 和 `DELETE /api/models/:id` 端点 + +**API 费用追踪(Token Cost Monitor)— 📊 Stats 标签页** +- 新增 `GET /api/stats` 端点,解析 `logs/gateway.log` 中的 token 用量 +- 支持 JSON 日志和文本格式日志(`input_tokens: N, output_tokens: N`) +- 按模型和按天汇总统计,显示 Token 用量和估计成本 +- 可视化:四张汇总卡片 + CSS 柱状图(按日) + 模型明细卡片 +- 支持自定义模型定价(存 `manager-config.json` 的 `modelPricing` 字段) +- 时间范围过滤:7 / 30 / 90 天 +- 点击标签页时懒加载,不影响首屏性能 + +**Cron 任务管理 — ⏰ Cron 标签页** +- 新增 `GET/POST/PUT/DELETE /api/cron` 四个端点,操作系统 crontab +- 只展示包含 `openclaw` 或 `ocm` 关键词的 cron 条目 +- 支持:查看、添加、启用/禁用(注释/取消注释)、手动触发、删除 +- 与 NAS 备份的 cron 条目联动(同样显示) +- 添加任务支持自定义 Cron 表达式 + 命令 + 标签 + +**Workspace 文件浏览器(全文件 + 可编辑)** +- 重写 `GET /api/workspace/:id`:动态扫描 workspace 目录下所有文件(不再硬编码 4 个文件名) +- 每个文件显示大小和最后修改时间 +- 默认只读视图(`
` 代码块),点击「编辑」按钮切换为 `
+      
+    
+    
+
+ + +
+
+
+
+
+ + + 留空则使用默认成长型提示词(推荐) +
+
+ + +
+
+
+
+
+
+ +
+ + + +
+ + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + +
+
+ + +
+
+ + +
+
+ + +
+ + +`; + +// ── HTTP 服务器 ─────────────────────────────────────────────── +const server = http.createServer(async (req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } + + const urlObj = new URL(req.url, `http://127.0.0.1:${PORT}`); + + try { + if (urlObj.pathname.startsWith('/api/')) { + const body = ['POST', 'PUT', 'PATCH'].includes(req.method) ? await parseBody(req) : {}; + await handleApi(req, res, urlObj, body); + } else { + const needsSetup = !(await configExists()); + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(needsSetup ? SETUP_HTML : MAIN_HTML); + } + } catch (err) { + console.error('[ERROR]', err.message); + if (!res.headersSent) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.message })); + } + } +}); + +server.listen(PORT, '127.0.0.1', () => { + const url = `http://localhost:${PORT}`; + console.log(''); + console.log('🦀 OpenClaw Manager v0.3'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('📂 目录:' + OPENCLAW_DIR); + console.log('🌐 地址:' + url); + console.log('💡 切换目录:node openclaw-manager.js --dir /path/to/.openclaw'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log('Ctrl+C 停止'); + openBrowser(url); +}); + +server.on('error', err => { + if (err.code === 'EADDRINUSE') + console.error(`❌ 端口 ${PORT} 已占用。请关闭其他程序后重试,或用 --port 指定其他端口。`); + else + console.error('❌ 启动失败:', err.message); + process.exit(1); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..17e03b2 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "openclaw-manager", + "version": "0.4.2", + "description": "A local web UI for managing OpenClaw AI agents — no npm install required", + "main": "openclaw-manager.js", + "scripts": { + "start": "node openclaw-manager.js", + "start:dir": "node openclaw-manager.js --dir" + }, + "engines": { + "node": ">=18" + }, + "keywords": [ + "openclaw", + "ai-agent", + "telegram-bot", + "llm", + "local-ui", + "no-dependencies" + ], + "author": "", + "license": "MIT", + "dependencies": {} +} diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..693a160 --- /dev/null +++ b/start.bat @@ -0,0 +1,170 @@ +@echo off +:: ================================================================ +:: OpenClaw Manager v0.5 — Windows 启动脚本 +:: +:: 特性: +:: * 自动检测 Node.js,未安装时给出安装指引(winget/scoop/官网) +:: * 自动检测 %USERPROFILE%\.openclaw 目录 +:: * 首次运行自动创建 manager-config.json +:: * 自动检测端口占用并建议替代端口 +:: * 支持 --dir / --port / --help 参数 +:: ================================================================ +setlocal enabledelayedexpansion +chcp 65001 >nul 2>&1 + +set "SCRIPT_DIR=%~dp0" +set "MANAGER_JS=%SCRIPT_DIR%openclaw-manager.js" +set "CONFIG_JSON=%SCRIPT_DIR%manager-config.json" +set "MIN_NODE_MAJOR=18" + +echo. +echo =================================== +echo OpenClaw Manager v0.5 +echo ----------------------------------- +echo. + +:: ── 检查主文件 ──────────────────────────────────────────────── +if not exist "%MANAGER_JS%" ( + echo [X] 找不到 openclaw-manager.js + echo 期望路径: %MANAGER_JS% + echo. + pause + exit /b 1 +) + +:: ── 检查 Node.js ───────────────────────────────────────────── +where node >nul 2>&1 +if errorlevel 1 ( + echo [X] 未找到 Node.js + echo. + echo 安装方式(选一种即可): + echo. + echo 1^) winget: winget install OpenJS.NodeJS.LTS + echo 2^) scoop: scoop install nodejs-lts + echo 3^) 官网下载: https://nodejs.org/ + echo 4^) nvm-windows: https://github.com/coreybutler/nvm-windows + echo. + pause + exit /b 1 +) + +:: 获取主版本号并检查 +for /f "tokens=1 delims=." %%v in ('node -e "process.stdout.write(process.versions.node)"') do ( + set "NODE_MAJOR=%%v" +) + +if !NODE_MAJOR! LSS %MIN_NODE_MAJOR% ( + echo [!] Node.js 版本过低(需要 ^>= v%MIN_NODE_MAJOR%) + echo. + echo 安装方式(选一种即可): + echo. + echo 1^) winget: winget install OpenJS.NodeJS.LTS + echo 2^) scoop: scoop install nodejs-lts + echo 3^) 官网下载: https://nodejs.org/ + echo. + pause + exit /b 1 +) + +for /f "tokens=*" %%v in ('node -e "process.stdout.write('v'+process.versions.node)"') do ( + echo [OK] Node.js %%v +) + +:: ── 解析参数 ───────────────────────────────────────────────── +set "DIR_ARG=" +set "PORT_ARG=" +set "EXTRA_ARGS=" + +:parse_args +if "%~1"=="" goto :after_args +if /i "%~1"=="--help" goto :show_help +if /i "%~1"=="-h" goto :show_help +if /i "%~1"=="--dir" ( + set "DIR_ARG=%~2" + shift & shift + goto :parse_args +) +if /i "%~1"=="--port" ( + set "PORT_ARG=%~2" + shift & shift + goto :parse_args +) +set "EXTRA_ARGS=!EXTRA_ARGS! %~1" +shift +goto :parse_args + +:show_help +echo 用法: start.bat [选项] +echo. +echo 选项: +echo --dir ^<路径^> OpenClaw 配置目录(默认 %%USERPROFILE%%\.openclaw) +echo --port ^<端口^> 监听端口(默认 3333) +echo --help 显示帮助 +echo. +exit /b 0 + +:after_args + +:: ── 检测 OpenClaw 配置目录 ─────────────────────────────────── +set "OPENCLAW_DIR=" +if defined DIR_ARG ( + set "OPENCLAW_DIR=!DIR_ARG!" +) else if exist "%CONFIG_JSON%" ( + for /f "tokens=*" %%d in ('node -e "try{const c=JSON.parse(require('fs').readFileSync('%CONFIG_JSON%','utf8'));const d=c.openclawDir||c.dir||'';process.stdout.write(d.replace(/^~/,require('os').homedir()))}catch{}"') do ( + set "OPENCLAW_DIR=%%d" + ) +) +if not defined OPENCLAW_DIR set "OPENCLAW_DIR=%USERPROFILE%\.openclaw" + +if exist "!OPENCLAW_DIR!" ( + echo [OK] 配置目录: !OPENCLAW_DIR! +) else ( + echo [!] 配置目录不存在: !OPENCLAW_DIR! + echo 首次使用?请先运行 openclaw onboard 初始化。 +) + +:: ── 首次运行:自动创建 manager-config.json ──────────────────── +if not exist "%CONFIG_JSON%" ( + echo [-^>] 首次运行,创建 manager-config.json + node -e "require('fs').writeFileSync('%CONFIG_JSON%',JSON.stringify({dir:'~/.openclaw'},null,2))" +) + +:: ── 检查端口可用性 ─────────────────────────────────────────── +set "PORT=3333" +if defined PORT_ARG set "PORT=!PORT_ARG!" + +netstat -an 2>nul | findstr /r ":%PORT% .*LISTENING" >nul 2>&1 +if not errorlevel 1 ( + echo [!] 端口 !PORT! 已被占用 + for %%p in (3334 3335 3336 8080 8888) do ( + netstat -an 2>nul | findstr /r ":%%p .*LISTENING" >nul 2>&1 + if errorlevel 1 ( + echo 尝试备用端口 %%p... + set "PORT=%%p" + set "PORT_ARG=%%p" + goto :port_ok + ) + ) +) +:port_ok + +:: ── 构建启动命令 ───────────────────────────────────────────── +set "CMD_ARGS=" +if defined DIR_ARG set "CMD_ARGS=!CMD_ARGS! --dir "!DIR_ARG!"" +if defined PORT_ARG set "CMD_ARGS=!CMD_ARGS! --port !PORT_ARG!" +if defined EXTRA_ARGS set "CMD_ARGS=!CMD_ARGS! !EXTRA_ARGS!" + +echo. +echo [^>] 启动中 -^> http://localhost:!PORT! +echo Ctrl+C 停止 +echo. + +:: ── 启动 ───────────────────────────────────────────────────── +node "%MANAGER_JS%"!CMD_ARGS! + +if errorlevel 1 ( + echo. + echo [X] 启动失败,请检查上方错误信息。 + pause +) +endlocal diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..6ab721f --- /dev/null +++ b/start.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +# ================================================================ +# OpenClaw Manager v0.5 — 跨平台启动脚本 (macOS / Linux) +# +# 特性: +# • 自动检测 Node.js,未安装时给出安装指引 +# • 自动检测 ~/.openclaw 目录,不存在时提示 +# • 首次运行自动创建 manager-config.json +# • 自动检测端口占用并建议替代端口 +# • 支持 --dir / --port / --help 参数 +# ================================================================ +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MANAGER_JS="$SCRIPT_DIR/openclaw-manager.js" +CONFIG_JSON="$SCRIPT_DIR/manager-config.json" +MIN_NODE_MAJOR=18 + +# ── 颜色 ───────────────────────────────────────────────────── +RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m' +CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m' + +banner() { + echo "" + echo -e "${CYAN}${BOLD} 🦀 OpenClaw Manager v0.5${RESET}" + echo -e "${DIM} ─────────────────────────────${RESET}" + echo "" +} + +banner + +# ── 检查主文件 ──────────────────────────────────────────────── +if [ ! -f "$MANAGER_JS" ]; then + echo -e "${RED}✗ 找不到 openclaw-manager.js${RESET}" + echo -e " 期望路径: ${DIM}$MANAGER_JS${RESET}" + exit 1 +fi + +# ── 检查 Node.js ───────────────────────────────────────────── +install_node_hint() { + echo "" + echo -e " ${BOLD}安装方式(选一种即可):${RESET}" + echo "" + if [[ "$(uname)" == "Darwin" ]]; then + echo -e " ${GREEN}1)${RESET} Homebrew: ${CYAN}brew install node${RESET}" + echo -e " ${GREEN}2)${RESET} 官网下载: ${CYAN}https://nodejs.org/${RESET}" + echo -e " ${GREEN}3)${RESET} nvm: ${CYAN}curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash${RESET}" + echo -e " ${DIM}然后 nvm install --lts${RESET}" + else + echo -e " ${GREEN}1)${RESET} apt: ${CYAN}sudo apt install -y nodejs npm${RESET}" + echo -e " ${GREEN}2)${RESET} 官网下载: ${CYAN}https://nodejs.org/${RESET}" + echo -e " ${GREEN}3)${RESET} nvm: ${CYAN}curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash${RESET}" + echo -e " ${DIM}然后 nvm install --lts${RESET}" + fi + echo "" +} + +if ! command -v node &>/dev/null; then + echo -e "${RED}✗ 未找到 Node.js${RESET}" + install_node_hint + exit 1 +fi + +NODE_VER=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))") +if [ "$NODE_VER" -lt "$MIN_NODE_MAJOR" ]; then + echo -e "${YELLOW}⚠ Node.js 版本过低(当前 v$(node -v),需要 >= v${MIN_NODE_MAJOR})${RESET}" + install_node_hint + exit 1 +fi +echo -e " ${GREEN}✓${RESET} Node.js $(node -v)" + +# ── 解析参数 ───────────────────────────────────────────────── +DIR_ARG="" +PORT_ARG="" +EXTRA_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --dir) DIR_ARG="$2"; shift 2;; + --dir=*) DIR_ARG="${1#*=}"; shift;; + --port) PORT_ARG="$2"; shift 2;; + --port=*) PORT_ARG="${1#*=}"; shift;; + --help|-h) + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " --dir <路径> OpenClaw 配置目录(默认 ~/.openclaw)" + echo " --port <端口> 监听端口(默认 3333)" + echo " --help 显示帮助" + exit 0;; + *) EXTRA_ARGS+=("$1"); shift;; + esac +done + +# ── 检测 OpenClaw 配置目录 ─────────────────────────────────── +# 优先级: --dir 参数 > manager-config.json > 环境变量 > 默认 +OPENCLAW_DIR="" +if [ -n "$DIR_ARG" ]; then + OPENCLAW_DIR="${DIR_ARG/#\~/$HOME}" +elif [ -f "$CONFIG_JSON" ]; then + OPENCLAW_DIR=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('$CONFIG_JSON','utf8'));const d=c.openclawDir||c.dir||'';process.stdout.write(d.replace(/^~/,require('os').homedir()))}catch{}" 2>/dev/null) +fi +if [ -z "$OPENCLAW_DIR" ] && [ -n "$OPENCLAW_DIR_ENV" ]; then + OPENCLAW_DIR="$OPENCLAW_DIR_ENV" +fi +[ -z "$OPENCLAW_DIR" ] && OPENCLAW_DIR="$HOME/.openclaw" + +if [ -d "$OPENCLAW_DIR" ]; then + echo -e " ${GREEN}✓${RESET} 配置目录: ${DIM}$OPENCLAW_DIR${RESET}" +else + echo -e " ${YELLOW}⚠${RESET} 配置目录不存在: ${DIM}$OPENCLAW_DIR${RESET}" + echo -e " ${DIM}首次使用?请先运行 openclaw onboard 初始化。${RESET}" +fi + +# ── 首次运行:自动创建 manager-config.json ──────────────────── +if [ ! -f "$CONFIG_JSON" ]; then + echo -e " ${CYAN}→${RESET} 首次运行,创建 ${DIM}manager-config.json${RESET}" + node -e "require('fs').writeFileSync('$CONFIG_JSON',JSON.stringify({dir:'~/.openclaw'},null,2))" +fi + +# ── 检查端口可用性 ─────────────────────────────────────────── +PORT="${PORT_ARG:-3333}" + +check_port() { + if command -v lsof &>/dev/null; then + lsof -i :"$1" -sTCP:LISTEN >/dev/null 2>&1 && return 1 + elif command -v ss &>/dev/null; then + ss -tlnp 2>/dev/null | grep -q ":$1 " && return 1 + elif command -v netstat &>/dev/null; then + netstat -tlnp 2>/dev/null | grep -q ":$1 " && return 1 + fi + return 0 +} + +if ! check_port "$PORT"; then + echo -e " ${YELLOW}⚠${RESET} 端口 ${PORT} 已被占用" + # 尝试找一个可用端口 + for ALT in 3334 3335 3336 8080 8888; do + if check_port "$ALT"; then + echo -e " ${DIM}尝试备用端口 ${ALT}...${RESET}" + PORT="$ALT" + PORT_ARG="$ALT" + break + fi + done +fi + +# ── 构建启动命令 ───────────────────────────────────────────── +CMD_ARGS=() +[ -n "$DIR_ARG" ] && CMD_ARGS+=(--dir "$DIR_ARG") +[ -n "$PORT_ARG" ] && CMD_ARGS+=(--port "$PORT_ARG") +CMD_ARGS+=("${EXTRA_ARGS[@]}") + +echo "" +echo -e " ${CYAN}▶${RESET} 启动中 → ${BOLD}http://localhost:${PORT}${RESET}" +echo -e " ${DIM}Ctrl+C 停止${RESET}" +echo "" + +# ── 启动 ───────────────────────────────────────────────────── +exec node "$MANAGER_JS" "${CMD_ARGS[@]}"