@clawhub-mslchy-6be20c3bc6
用于管理和维护 OpenClaw 工作区的结构。当用户提到工作区混乱、需要整理文件夹、或者希望建立标准目录结构时使用。提供自动归档、分类、清理和健康审计功能。
---
name: workspace-manager
description: |
用于管理和维护 OpenClaw 工作区的结构。当用户提到工作区混乱、需要整理文件夹、或者希望建立标准目录结构时使用。提供自动归档、分类、清理和健康审计功能。
metadata:
pattern: ["pipeline", "tool-wrapper"]
---
### Design Pattern
此技能使用 **Pipeline(流水线)模式** 来执行多步骤工作区管理,并通过 **Tool Wrapper(工具包装器)模式** 安全地封装文件系统操作。**优先调用配套脚本**,避免在 Prompt 中内联复杂 bash 命令,以控制 Token 成本并提升执行稳定性。
# Workspace Manager
此技能旨在保持工作区的整洁和高效,将人类文件与 Agent 文件分离,并提供一站式维护能力。
---
## 标准目录结构 (Standard Structure)
```
~/.openclaw/workspace/
├── Workspace_Human/ # ❶ 供人类使用的文件(输入、输出、备份)
│ ├── input/ # 用户提供或导入的原始文件
│ ├── output/ # 生成的产物(图片、PDF、文档)
│ │ ├── images/ # 图片文件
│ │ ├── docs/ # 文档文件 (PDF, DOCX)
│ │ └── data/ # 结构化数据 (JSON, CSV)
│ ├── backup/ # 备份文件(手动 + 自动)
│ └── temp/ # 临时文件(CDP 截图、缓存等,可安全清理)
│
├── Workspace_Agent/ # ❷ 供 Agent 之间交互的文件
│ ├── memory/ # 每日日志 (YYYY-MM-DD.md) + 长期记忆
│ ├── skills/ # 已安装的技能目录
│ ├── subagents/ # 永久子 agent 配置
│ ├── shared_context/ # 多 agent 共享上下文
│ ├── artifacts/ # 中间产物(构建产物、待整理文件)
│ ├── cache/ # 可复用的缓存数据
│ ├── logs/ # 操作日志
│ ├── skills_custom/ # 自定义编写的技能
│ ├── prompts/ # 常用提示词模板
│ └── kb/ # Agent 知识库
│
├── archive/ # 归档目录(按 YYYY-MM/ 组织)
├── scripts/ # 本地脚本(workspace-manager 专用)
└── secret/ # 敏感凭据
```
---
## 核心文件保护(永不删除)
以下文件无论任何情况都受到保护:
- `MEMORY.md`, `SOUL.md`, `USER.md`, `AGENTS.md`, `HEARTBEAT.md`
- `.git/`, `memory/`, `skills/`, `subagents/`, `Workspace_Human/`
---
## Pipeline 工作流
**所有步骤均通过配套脚本执行**。推荐使用一键全量 Pipeline,复杂场景可单独触发某一阶段。
### 推荐:一键全量 Pipeline
```bash
# 执行完整 5 步流水线(Audit → Organize → Clean → Archive → Sync)
bash {{SKILL_DIR}}/scripts/pipeline.sh --all
# 仅预览(不实际执行任何写入操作)
bash {{SKILL_DIR}}/scripts/pipeline.sh --all --dry-run
# 执行指定步骤
bash {{SKILL_DIR}}/scripts/pipeline.sh audit organize
```
---
### Step 1 — 健康审计 (Audit)
```bash
bash {{SKILL_DIR}}/scripts/health-check.sh
```
自动检查:断链、空目录、大文件(>10MB)、畸形命名、磁盘占用、最近活动。
输出 0-100 健康评分及分级建议。
### Step 2 — 规范化 (Standardize)
```bash
bash {{SKILL_DIR}}/scripts/standardize.sh
```
确保标准目录结构完整,检测根目录散落文件并给出整理建议。
### Step 3 — 自动整理 (Organize)
```bash
bash {{SKILL_DIR}}/scripts/organize.sh
```
将 `Workspace_Agent/artifacts/` 中的散落文件按类型移动到 `Workspace_Human/output/` 对应子目录:
| 文件类型 | 目标位置 |
|----------|----------|
| `*.png`, `*.jpg`, `*.webp`, `*.gif` | `Workspace_Human/output/images/` |
| `*.pdf`, `*.docx`, `*.doc` | `Workspace_Human/output/docs/` |
| `*.json`, `*.csv`, `*.xml` | `Workspace_Human/output/data/` |
| `*screenshot*`, `cdp_tmp_*`, `*.tmp` | `Workspace_Human/temp/` |
### Step 4 — 安全清理 (Clean)
```bash
# 预览(默认,永远先预览)
python3 {{SKILL_DIR}}/scripts/cleanup.py
# 执行清理(移动到系统 trash,可恢复)
python3 {{SKILL_DIR}}/scripts/cleanup.py --execute
# 按条件清理
python3 {{SKILL_DIR}}/scripts/cleanup.py --min-age 30 --execute # 30天以上
python3 {{SKILL_DIR}}/scripts/cleanup.py --min-size 50 # >50MB
```
**保护规则**:永不删除 `.git/`、`memory/`、`skills/`、`Workspace_Human/`、最近 24h 文件及所有核心配置文件。
### Step 5 — 归档 (Archive)
```bash
# 交互式归档(7天以上文件,按月组织)
bash {{SKILL_DIR}}/scripts/archive.sh
# 自定义天数
DAYS=30 bash {{SKILL_DIR}}/scripts/archive.sh
```
归档结构:`archive/YYYY-MM/`。仅处理 `Workspace_Agent/artifacts/`。
### Step 5 — 云端同步 (Sync) ⭐ 可选扩展
```bash
bash {{SKILL_DIR}}/scripts/sync.sh
```
**此步骤为可选扩展**,需要 `gog` CLI 已安装并认证。未安装或未认证时自动跳过,不阻断其他 Pipeline 步骤。
**同步范围**(可通过 `config/sync-config.json` 配置开关):
- ✅ `Workspace_Human/` 全部内容 → Google Drive `AI_Workspace/Workspace_Human/`
- ✅ `Workspace_Agent/` 全部内容 → Google Drive `AI_Workspace/Workspace_Agent/`(默认关闭)
- ✅ 核心配置文件(MEMORY.md 等) → Google Drive `AI_Workspace_Backup/`
**启用同步**:安装 `gog` CLI 并运行 `gog auth login` 即可自动启用。
**关闭同步**:删除 `gog` 或不运行 `gog auth login`,Pipeline 自动跳过此步骤。
---
## 常用命令速查
| 任务 | 命令 |
|------|------|
| **一键全量** | `bash {{SKILL_DIR}}/scripts/pipeline.sh --all` |
| 健康审计 | `bash {{SKILL_DIR}}/scripts/health-check.sh` |
| 规范化 | `bash {{SKILL_DIR}}/scripts/standardize.sh` |
| 整理文件 | `bash {{SKILL_DIR}}/scripts/organize.sh` |
| 预览清理 | `python3 {{SKILL_DIR}}/scripts/cleanup.py` |
| 执行清理 | `python3 {{SKILL_DIR}}/scripts/cleanup.py --execute` |
| 归档旧文件 | `bash {{SKILL_DIR}}/scripts/archive.sh` |
| 云端同步 | `bash {{SKILL_DIR}}/scripts/sync.sh` ⭐可选 |
---
## 目录命名规则
- **所有目录名**:kebab-case,无空格,无特殊字符
- 示例:`Workspace_Human`, `Workspace_Agent`, `shared_context`, `skills_custom`
- **文件扩展名**:全部小写
## 最佳实践
- **每次会话结束前**:`bash {{SKILL_DIR}}/scripts/pipeline.sh audit organize`
- **每周定期维护**:`bash {{SKILL_DIR}}/scripts/pipeline.sh --all`(完整流水线)
- **永远先预览再执行**:特别是 `cleanup.py`(默认就是预览模式)
- **永远用 `trash` 代替 `rm`**:所有脚本均使用 `trash-put`,误删可从系统回收站恢复
- 引导用户将临时文件生成在 `Workspace_Agent/artifacts/`,会话结束后由 Pipeline 自动整理
FILE:CHANGELOG.md
# Changelog
All notable changes to this skill will be documented in this file.
## [1.0.0] - 2026-03-19
### Added
- **SKILL.md** — Complete skill definition with Pipeline + Tool Wrapper design patterns
- **Standard Directory Structure** — Dual workspace architecture:
- `Workspace_Human/` — Human-facing files (input, output, backup, temp)
- `Workspace_Agent/` — Agent-only files (memory, skills, subagents, shared_context, artifacts, cache, logs, skills_custom, prompts, kb)
- **Pipeline Orchestrator** (`scripts/pipeline.sh`) — Master script that runs all 5 steps in sequence: Audit → Organize → Clean → Archive → Sync
- Supports `--all` (full pipeline), `--dry-run` (preview only), and step selection (e.g., `audit organize`)
- Optional sync step gracefully skipped if gog CLI is not configured
- **Health Check** (`scripts/health-check.sh`) — Workspace entropy audit with 0-100 health score:
- Broken symlinks detection
- Empty directory detection
- Large file detection (>10MB)
- Malformed filename detection
- Disk usage by directory
- Recent activity summary
- **Standardize** (`scripts/standardize.sh`) — Ensures all standard directories exist and detects misplaced files in root
- **Organize** (`scripts/organize.sh`) — Auto-classifies files from `Workspace_Agent/artifacts/` into `Workspace_Human/output/` subdirectories by file type
- **Cleanup** (`scripts/cleanup.py`) — Safe Python-based cleanup with dry-run default:
- Moves files to system trash (recoverable via `trash-put`)
- Configurable patterns via `config/patterns.json`
- Age and size filters
- JSON output for automation
- **Archive** (`scripts/archive.sh`) — Interactive archiver that moves files older than N days into `archive/YYYY-MM/` structure
- **Sync** (`scripts/sync.sh`) — Optional Google Drive sync via `gog` CLI:
- Syncs `Workspace_Human/` → `AI_Workspace/`
- Syncs `Workspace_Agent/` → `AI_Workspace/` (opt-in)
- Backs up core config files → `AI_Workspace_Backup/`
- Gracefully skipped if gog is not installed/authenticated
- **Config Files**:
- `config/patterns.json` — Customizable cleanup patterns and protected paths
- `config/sync-config.json` — Sync toggle switches and folder names
### Design Patterns
- **Pipeline** — Strict 5-step workflow with checkpoint gating
- **Tool Wrapper** — File system operations encapsulated in reusable scripts
### Security Features
- Never uses `rm` — all deletions go through `trash-put` (recoverable)
- Default dry-run on all destructive operations
- Protected paths whitelist (core config files, .git, memory/, skills/, Workspace_Human/)
- 24-hour recent-file protection
### Recommended Usage
```bash
# Full pipeline (recommended for weekly maintenance)
bash {{SKILL_DIR}}/scripts/pipeline.sh --all
# Quick session cleanup
bash {{SKILL_DIR}}/scripts/pipeline.sh audit organize
# Preview what would be cleaned
python3 {{SKILL_DIR}}/scripts/cleanup.py
```
FILE:scripts/organize.sh
#!/usr/bin/env bash
# Workspace Organizer - Move files to proper locations
# Part of workspace-manager skill
set -euo pipefail
WORKSPACE="-$HOME/.openclaw/workspace"
HUMAN="$WORKSPACE/Workspace_Human"
AGENT="$WORKSPACE/Workspace_Agent"
ARTIFACTS="$AGENT/artifacts"
echo "=========================================="
echo " 📂 Workspace Organizer"
echo "=========================================="
echo "Date: $(date '+%Y-%m-%d %H:%M')"
echo ""
# Ensure directories exist
mkdir -p "$HUMAN/input"
mkdir -p "$HUMAN/output/images"
mkdir -p "$HUMAN/output/docs"
mkdir -p "$HUMAN/output/data"
mkdir -p "$HUMAN/backup"
mkdir -p "$HUMAN/temp"
mkdir -p "$AGENT/memory"
mkdir -p "$AGENT/skills"
mkdir -p "$AGENT/subagents"
mkdir -p "$AGENT/shared_context"
mkdir -p "$AGENT/artifacts"
mkdir -p "$AGENT/cache"
mkdir -p "$AGENT/logs"
mkdir -p "$AGENT/skills_custom"
mkdir -p "$AGENT/prompts"
mkdir -p "$AGENT/kb"
echo "✅ Directory structure verified"
echo ""
# Organize artifacts
echo "📦 Organizing artifacts..."
MOVED=0
# Images from artifacts -> Workspace_Human/output/images
find "$ARTIFACTS" -maxdepth 1 -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.gif" -o -name "*.webp" \) 2>/dev/null | while read -r file; do
mv "$file" "$HUMAN/output/images/"
echo " 📷 $(basename "$file") -> output/images/"
MOVED=$((MOVED + 1))
done
# Docs from artifacts -> Workspace_Human/output/docs
find "$ARTIFACTS" -maxdepth 1 -type f \( -name "*.pdf" -o -name "*.docx" -o -name "*.doc" \) 2>/dev/null | while read -r file; do
mv "$file" "$HUMAN/output/docs/"
echo " 📄 $(basename "$file") -> output/docs/"
MOVED=$((MOVED + 1))
done
# Data files from artifacts -> Workspace_Human/output/data
find "$ARTIFACTS" -maxdepth 1 -type f \( -name "*.json" -o -name "*.csv" -o -name "*.xml" \) 2>/dev/null | while read -r file; do
mv "$file" "$HUMAN/output/data/"
echo " 📊 $(basename "$file") -> output/data/"
MOVED=$((MOVED + 1))
done
# Temp files -> Workspace_Human/temp
find "$ARTIFACTS" -maxdepth 1 -type f \( -name "*screenshot*" -o -name "cdp_tmp_*" -o -name "*.tmp" \) 2>/dev/null | while read -r file; do
mv "$file" "$HUMAN/temp/"
echo " 🕐 $(basename "$file") -> temp/"
MOVED=$((MOVED + 1))
done
# Files in workspace root -> artifacts
find "$WORKSPACE" -maxdepth 1 -type f \( -name "*.png" -o -name "*.jpg" -o -name "*.pdf" -o -name "*.json" -o -name "*.csv" \) 2>/dev/null | grep -v -E "^(MEMORY|SOUL|USER|AGENTS|HEARTBEAT|TOOLS)" | while read -r file; do
mv "$file" "$ARTIFACTS/"
echo " 📦 $(basename "$file") -> artifacts/"
MOVED=$((MOVED + 1))
done
echo ""
if [ $MOVED -eq 0 ]; then
echo "✅ No files to organize."
else
echo "✅ Organized $MOVED files."
fi
FILE:scripts/health-check.sh
#!/usr/bin/env bash
# Workspace Health Check - Audit workspace health and entropy
# Part of workspace-manager skill
set -euo pipefail
WORKSPACE="-$HOME/.openclaw/workspace"
SCORE=100
ISSUES=()
echo "=========================================="
echo " 🏥 Workspace Health Check"
echo "=========================================="
echo "Date: $(date '+%Y-%m-%d %H:%M')"
echo "Path: $WORKSPACE"
echo ""
# 1. Broken symlinks
echo "1. Checking for broken symlinks..."
BROKEN=$(find "$WORKSPACE" -type l ! -exec test -e {} \; -print 2>/dev/null || true)
if [ -n "$BROKEN" ]; then
SCORE=$((SCORE - 10 * $(echo "$BROKEN" | wc -l)))
ISSUES+=("🔴 Broken symlinks found:")
while IFS= read -r link; do
ISSUES+=(" • $link")
done <<< "$BROKEN"
echo " ⚠️ Found $(echo "$BROKEN" | wc -l) broken symlinks"
else
echo " ✅ No broken symlinks"
fi
echo ""
# 2. Empty directories
echo "2. Checking for empty directories..."
EMPTY=$(find "$WORKSPACE" -mindepth 1 -type d -empty 2>/dev/null | grep -v -E "(node_modules|\.git|memory|Workspace_Human|Workspace_Agent)" || true)
if [ -n "$EMPTY" ]; then
COUNT=$(echo "$EMPTY" | wc -l)
SCORE=$((SCORE - 2 * COUNT))
ISSUES+=("🟡 Empty directories: $COUNT")
echo " ⚠️ Found $COUNT empty directories"
else
echo " ✅ No empty directories"
fi
echo ""
# 3. Large files
echo "3. Checking for large files (>10MB)..."
LARGE=$(find "$WORKSPACE" -mindepth 1 -type f -size +10M 2>/dev/null | head -10 || true)
if [ -n "$LARGE" ]; then
COUNT=$(echo "$LARGE" | wc -l)
SCORE=$((SCORE - 5 * COUNT))
ISSUES+=("🟠 Large files found: $COUNT")
echo " ⚠️ Found large files:"
while IFS= read -r file; do
SIZE=$(du -h "$file" 2>/dev/null | cut -f1)
echo " • $SIZE - $(basename "$file")"
done <<< "$LARGE"
else
echo " ✅ No large files"
fi
echo ""
# 4. Malformed names (spaces, special chars)
echo "4. Checking for malformed names..."
MALFORMED=$(find "$WORKSPACE" -mindepth 1 \( -name "*[ ]*" -o -name "*[(){}[\]]*" -o -name "*[>&<|]*" \) 2>/dev/null | grep -v -E "(Workspace_Human|Workspace_Agent)" | head -10 || true)
if [ -n "$MALFORMED" ]; then
COUNT=$(echo "$MALFORMED" | wc -l)
SCORE=$((SCORE - 3 * COUNT))
ISSUES+=("🟡 Malformed names: $COUNT")
echo " ⚠️ Found $COUNT problematic names:"
while IFS= read -r file; do
echo " • $(basename "$file")"
done <<< "$MALFORMED"
else
echo " ✅ No malformed names"
fi
echo ""
# 5. Disk usage by top-level directory
echo "5. Disk usage by directory:"
du -sh "$WORKSPACE"/* 2>/dev/null | sort -hr | head -10 | while IFS= read -r line; do
echo " $line"
done
echo ""
# 6. File counts
echo "6. File statistics:"
TOTAL_FILES=$(find "$WORKSPACE" -type f 2>/dev/null | wc -l)
TOTAL_DIRS=$(find "$WORKSPACE" -type d 2>/dev/null | wc -l)
echo " 📁 Total files: $TOTAL_FILES"
echo " 📂 Total directories: $TOTAL_DIRS"
echo ""
# 7. Recent changes (last 24h)
echo "7. Recently modified (last 24h):"
RECENT=$(find "$WORKSPACE" -type f -mtime -1 2>/dev/null | grep -v -E "(\.git|node_modules)" | head -10 || true)
if [ -n "$RECENT" ]; then
echo " Recent activity:"
while IFS= read -r file; do
echo " • $(basename "$file")"
done <<< "$RECENT"
else
echo " ℹ️ No recent changes"
fi
echo ""
# Final score
echo "=========================================="
if [ $SCORE -ge 90 ]; then
STATUS="🟢 Healthy"
elif [ $SCORE -ge 70 ]; then
STATUS="🟡 Fair"
elif [ $SCORE -ge 50 ]; then
STATUS="🟠 Degraded"
else
STATUS="🔴 Critical"
fi
echo "Health Score: $SCORE/100 - $STATUS"
echo "=========================================="
echo ""
# Recommendations
if [ #ISSUES[@] -gt 0 ]; then
echo "📋 Recommendations:"
for issue in "ISSUES[@]"; do
echo " $issue"
done
echo ""
echo "💡 Run cleanup: python3 {{SKILL_DIR}}/scripts/cleanup.py --execute"
fi
FILE:scripts/standardize.sh
#!/usr/bin/env bash
# Workspace Standardize - Ensure standard directory structure exists
# Part of workspace-manager skill
set -euo pipefail
WORKSPACE="-$HOME/.openclaw/workspace"
echo "=========================================="
echo " 🏗️ Workspace Standardizer"
echo "=========================================="
echo "Date: $(date '+%Y-%m-%d %H:%M')"
echo "Path: $WORKSPACE"
echo ""
# Define all required directories
HUMAN_DIRS=(
"$WORKSPACE/Workspace_Human/input"
"$WORKSPACE/Workspace_Human/output/images"
"$WORKSPACE/Workspace_Human/output/docs"
"$WORKSPACE/Workspace_Human/output/data"
"$WORKSPACE/Workspace_Human/backup"
"$WORKSPACE/Workspace_Human/temp"
)
AGENT_DIRS=(
"$WORKSPACE/Workspace_Agent/memory"
"$WORKSPACE/Workspace_Agent/skills"
"$WORKSPACE/Workspace_Agent/subagents"
"$WORKSPACE/Workspace_Agent/shared_context"
"$WORKSPACE/Workspace_Agent/artifacts"
"$WORKSPACE/Workspace_Agent/cache"
"$WORKSPACE/Workspace_Agent/logs"
"$WORKSPACE/Workspace_Agent/skills_custom"
"$WORKSPACE/Workspace_Agent/prompts"
"$WORKSPACE/Workspace_Agent/kb"
)
SYSTEM_DIRS=(
"$WORKSPACE/archive"
"$WORKSPACE/scripts"
"$WORKSPACE/secret"
)
ALL_DIRS=("HUMAN_DIRS[@]" "AGENT_DIRS[@]" "SYSTEM_DIRS[@]")
CREATED=0
EXISTING=0
for dir in "ALL_DIRS[@]"; do
if [ -d "$dir" ]; then
EXISTING=$((EXISTING + 1))
else
mkdir -p "$dir"
echo " ✅ Created: dir#$WORKSPACE/"
CREATED=$((CREATED + 1))
fi
done
echo ""
echo "📊 Summary:"
echo " Existing: $EXISTING directories"
echo " Created: $CREATED directories"
echo ""
# Check for misplaced files in root
echo "📋 Checking root directory for misplaced files..."
ROOT_FILES=$(find "$WORKSPACE" -maxdepth 1 -type f 2>/dev/null | grep -v -E "^(MEMORY|SOUL|USER|AGENTS|HEARTBEAT|TOOLS|IDENTITY)" || true)
if [ -z "$ROOT_FILES" ]; then
echo " ✅ Root directory is clean."
else
COUNT=$(echo "$ROOT_FILES" | wc -l)
echo " ⚠️ Found $COUNT misplaced file(s) in root:"
echo "$ROOT_FILES" | while read -r f; do
echo " • $(basename "$f")"
done
echo ""
echo " 💡 Run 'organize.sh' to move these to Workspace_Human/"
fi
echo ""
echo "✅ Standardization complete."
FILE:scripts/sync.sh
#!/usr/bin/env bash
# Workspace Sync - Backup workspace to Google Drive via gog
# Part of workspace-manager skill
# ⚠️ This is an OPTIONAL extension. Requires 'gog' CLI to be installed and authenticated.
# If gog is not available, skip gracefully without blocking other pipeline steps.
set -euo pipefail
WORKSPACE="-$HOME/.openclaw/workspace"
HUMAN="$WORKSPACE/Workspace_Human"
AGENT="$WORKSPACE/Workspace_Agent"
SKILL_DIR="$(cd "$(dirname "BASH_SOURCE[0]")/.." && pwd)"
# Load optional config
CONFIG_FILE="$SKILL_DIR/config/sync-config.json"
if [ -f "$CONFIG_FILE" ]; then
source <(jq -r 'to_entries | .[] | "SYNC_\(.key|ascii_upcase)=\(.value)"' "$CONFIG_FILE" 2>/dev/null || true)
fi
# Sync config (all optional, defaults to false to be conservative)
SYNC_HUMAN="-true"
SYNC_AGENT="-false"
SYNC_BACKUP="-true"
OUTPUT_FOLDER="-AI_Workspace"
BACKUP_FOLDER="-AI_Workspace_Backup"
echo "=========================================="
echo " ☁️ Workspace Sync (Optional)"
echo "=========================================="
echo "Date: $(date '+%Y-%m-%d %H:%M')"
echo ""
# Check if gog is available
if ! command -v gog &>/dev/null; then
echo "ℹ️ gog CLI not found. Sync skipped."
echo " To enable: go install github.com/cilaboratory/gog@latest"
echo " Then run: gog auth login"
exit 0
fi
# Check if gog is authenticated
if ! gog whoami &>/dev/null; then
echo "ℹ️ gog not authenticated. Sync skipped."
echo " Run 'gog auth login' to enable sync."
exit 0
fi
SYNCED=0
FAILED=0
# Sync Workspace_Human/ (all contents)
if [ "$SYNC_HUMAN" = "true" ] && [ -d "$HUMAN" ]; then
echo "📁 Syncing Workspace_Human/..."
echo " → $OUTPUT_FOLDER/Workspace_Human/"
FILE_COUNT=$(find "$HUMAN" -type f 2>/dev/null | wc -l || echo 0)
echo " Files: $FILE_COUNT"
if [ "$FILE_COUNT" -eq 0 ]; then
echo " ℹ️ Nothing to sync"
elif gog drive sync upload "$HUMAN/" --folder "$OUTPUT_FOLDER" 2>/dev/null; then
echo " ✅ Workspace_Human synced"
SYNCED=$((SYNCED + 1))
else
echo " ❌ Failed"
FAILED=$((FAILED + 1))
fi
echo ""
fi
# Sync Workspace_Agent/ (all contents)
if [ "$SYNC_AGENT" = "true" ] && [ -d "$AGENT" ]; then
echo "🧠 Syncing Workspace_Agent/..."
echo " → $OUTPUT_FOLDER/Workspace_Agent/"
FILE_COUNT=$(find "$AGENT" -type f 2>/dev/null | wc -l || echo 0)
echo " Files: $FILE_COUNT"
if [ "$FILE_COUNT" -eq 0 ]; then
echo " ℹ️ Nothing to sync"
elif gog drive sync upload "$AGENT/" --folder "$OUTPUT_FOLDER" 2>/dev/null; then
echo " ✅ Workspace_Agent synced"
SYNCED=$((SYNCED + 1))
else
echo " ❌ Failed"
FAILED=$((FAILED + 1))
fi
echo ""
fi
# Backup core config files
if [ "$SYNC_BACKUP" = "true" ]; then
echo "💾 Backing up core configuration files..."
echo " → $BACKUP_FOLDER"
CORE_FILES=("MEMORY.md" "SOUL.md" "USER.md" "AGENTS.md" "IDENTITY.md")
BACKED_UP=0
for file in "CORE_FILES[@]"; do
if [ -f "$WORKSPACE/$file" ]; then
if gog drive sync upload "$WORKSPACE/$file" --folder "$BACKUP_FOLDER" 2>/dev/null; then
BACKED_UP=$((BACKED_UP + 1))
fi
fi
done
echo " ✅ Backed up $BACKED_UP core file(s)"
[ $BACKED_UP -gt 0 ] && SYNCED=$((SYNCED + 1))
echo ""
fi
echo "=========================================="
echo "📊 Sync Summary:"
echo " ✅ Successful: $SYNCED"
echo " ❌ Failed: $FAILED"
echo "=========================================="
[ $FAILED -gt 0 ] && exit 1 || exit 0
FILE:scripts/pipeline.sh
#!/usr/bin/env bash
# Workspace Pipeline - Master orchestrator for all 5 steps
# Part of workspace-manager skill
# Usage: pipeline.sh [step...] or pipeline.sh --all
# Steps: audit, organize, clean, archive, sync
set -euo pipefail
SKILL_DIR="$(cd "$(dirname "BASH_SOURCE[0]")/.." && pwd)"
WORKSPACE="-$HOME/.openclaw/workspace"
SCRIPT_DIR="$SKILL_DIR/scripts"
STEP_LOG="$WORKSPACE/Workspace_Agent/logs/pipeline-$(date '+%Y-%m-%d').log"
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "BLUE[$(date '+%H:%M:%S')]NC $1"; }
pass() { echo -e "GREEN✅NC $1"; }
warn() { echo -e "YELLOW⚠️ NC $1"; }
fail() { echo -e "RED❌NC $1"; }
usage() {
echo "Usage: $0 [OPTIONS] [STEPS]"
echo ""
echo "Options:"
echo " --all Run all steps (default)"
echo " --list List available steps"
echo " --dry-run Preview without executing"
echo ""
echo "Steps:"
echo " audit 1. Health check & scoring"
echo " organize 2. Sort files to proper locations"
echo " clean 3. Safe cleanup (dry-run first)"
echo " archive 4. Move old files to archive/"
echo " sync 5. Backup to Google Drive"
echo ""
echo "Examples:"
echo " $0 --all Run full pipeline"
echo " $0 audit organize Run only audit + organize"
echo " $0 --dry-run clean Preview cleanup without executing"
}
# Parse arguments
DRY_RUN=false
STEPS=()
MODE="all"
while [[ $# -gt 0 ]]; do
case "$1" in
--all) MODE="all"; shift ;;
--list)
echo "Available steps: audit, organize, clean, archive, sync"
exit 0 ;;
--dry-run) DRY_RUN=true; shift ;;
--*) usage; exit 1 ;;
*) STEPS+=("$1"); shift ;;
esac
done
# Set steps based on mode
if [ #STEPS[@] -eq 0 ] || [ "$MODE" = "all" ]; then
STEPS=("audit" "organize" "clean" "archive" "sync")
fi
# Ensure log directory exists
mkdir -p "$(dirname "$STEP_LOG")"
# Log pipeline start
echo "" | tee -a "$STEP_LOG"
echo "========================================" | tee -a "$STEP_LOG"
echo "🚀 Pipeline started: $(date '+%Y-%m-%d %H:%M:%S')" | tee -a "$STEP_LOG"
echo " Steps: STEPS[*]" | tee -a "$STEP_LOG"
echo " Dry-run: $DRY_RUN" | tee -a "$STEP_LOG"
echo "========================================" | tee -a "$STEP_LOG"
FAILED=0
for step in "STEPS[@]"; do
echo "" | tee -a "$STEP_LOG"
case "$step" in
audit)
log "🔍 Step 1/5: Running health check..."
if bash "$SCRIPT_DIR/health-check.sh" 2>&1 | tee -a "$STEP_LOG"; then
pass "Audit complete"
else
warn "Audit completed with warnings"
fi
;;
organize)
log "📂 Step 2/5: Running file organizer..."
if bash "$SCRIPT_DIR/organize.sh" 2>&1 | tee -a "$STEP_LOG"; then
pass "Organize complete"
else
warn "Organize completed with warnings"
fi
;;
clean)
log "🧹 Step 3/5: Running workspace cleaner..."
CLEAN_CMD="python3 $SCRIPT_DIR/cleanup.py"
if [ "$DRY_RUN" = "true" ]; then
CLEAN_CMD="$CLEAN_CMD --json"
log "(dry-run mode)"
else
CLEAN_CMD="$CLEAN_CMD --execute"
fi
if eval "$CLEAN_CMD" 2>&1 | tee -a "$STEP_LOG"; then
pass "Clean complete"
else
warn "Clean completed with warnings"
fi
;;
archive)
log "📦 Step 4/5: Running archiver..."
if [ "$DRY_RUN" = "true" ]; then
warn "Archive requires confirmation (skipped in dry-run)"
else
if bash "$SCRIPT_DIR/archive.sh" 2>&1 | tee -a "$STEP_LOG"; then
pass "Archive complete"
else
warn "Archive completed with warnings"
fi
fi
;;
sync)
log "☁️ Step 5/5: Running cloud sync (optional)..."
# Sync is optional - don't count as failure if skipped
if bash "$SCRIPT_DIR/sync.sh" 2>&1 | tee -a "$STEP_LOG"; then
pass "Sync complete"
else
SYNC_EXIT=PIPESTATUS[0]
if [ $SYNC_EXIT -eq 0 ]; then
pass "Sync skipped (gog not configured)"
else
warn "Sync completed with warnings"
fi
fi
;;
*)
fail "Unknown step: $step"
FAILED=$((FAILED + 1))
;;
esac
done
echo "" | tee -a "$STEP_LOG"
echo "========================================" | tee -a "$STEP_LOG"
echo "🏁 Pipeline finished: $(date '+%Y-%m-%d %H:%M:%S')" | tee -a "$STEP_LOG"
echo " Log: $STEP_LOG" | tee -a "$STEP_LOG"
echo "========================================" | tee -a "$STEP_LOG"
if [ $FAILED -gt 0 ]; then
fail "$FAILED step(s) failed"
exit 1
fi
pass "All steps completed successfully"
FILE:scripts/archive.sh
#!/usr/bin/env bash
# Workspace Archiver - Archive old files
# Part of workspace-manager skill
set -euo pipefail
WORKSPACE="-$HOME/.openclaw/workspace"
ARCHIVE="$WORKSPACE/archive"
ARTIFACTS="$WORKSPACE/Workspace_Agent/artifacts"
DAYS="-7"
echo "=========================================="
echo " 📦 Workspace Archiver"
echo "=========================================="
echo "Date: $(date '+%Y-%m-%d %H:%M')"
echo "Archive files older than: $DAYS days"
echo ""
# Create archive directory by month
MONTH=$(date '+%Y-%m')
mkdir -p "$ARCHIVE/$MONTH"
echo "📂 Scanning for files older than $DAYS days in artifacts..."
echo ""
# Find old files (excluding protected)
OLD_FILES=$(find "$ARTIFACTS" -maxdepth 1 -type f -mtime +$DAYS 2>/dev/null || true)
if [ -z "$OLD_FILES" ]; then
echo "✅ No files to archive."
exit 0
fi
COUNT=$(echo "$OLD_FILES" | wc -l)
echo "Found $COUNT files to archive:"
echo ""
# Show what will be archived
TOTAL_SIZE=0
while IFS= read -r file; do
SIZE=$(du -h "$file" 2>/dev/null | cut -f1)
echo " 📄 $SIZE - $(basename "$file")"
TOTAL_SIZE=$((TOTAL_SIZE + $(stat -c%s "$file" 2>/dev/null || echo 0)))
done <<< "$OLD_FILES"
echo ""
echo "Destination: $ARCHIVE/$MONTH/"
echo ""
# Confirm before archiving
read -p "⚠️ Proceed with archiving? (y/N): " confirm
if [ "-N" != "y" ]; then
echo "❌ Cancelled."
exit 0
fi
# Archive files
ARCHIVED=0
while IFS= read -r file; do
mv "$file" "$ARCHIVE/$MONTH/"
echo " ✅ $(basename "$file")"
ARCHIVED=$((ARCHIVED + 1))
done <<< "$OLD_FILES"
echo ""
echo "✅ Archived $ARCHIVED files to $ARCHIVE/$MONTH/"
FILE:scripts/cleanup.py
#!/usr/bin/env python3
"""
Workspace Cleaner - Safe automated cleanup for OpenClaw workspaces.
Dry-run by default, uses trash for safe deletion.
"""
import os
import sys
import json
import argparse
import subprocess
from pathlib import Path
from datetime import datetime, timedelta
from typing import List, Dict, Any
WORKSPACE = Path.home() / ".openclaw" / "workspace"
TRASH_DIR = Path.home() / ".local" / "share" / "Trash"
CONFIG_FILE = Path(__file__).parent.parent / "config" / "patterns.json"
# Default patterns
DEFAULT_CONFIG = {
"temp_extensions": [".tmp", ".bak", ".log", ".skill", ".cache"],
"temp_patterns": ["*~", "#*#", ".*.swp", "._*", ".DS_Store"],
"image_extensions": [".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"],
"protected_dirs": ["memory", "skills", "subagents", ".git", "Workspace_Human", "Workspace_Agent"],
"protected_files": ["MEMORY.md", "SOUL.md", "USER.md", "AGENTS.md", "HEARTBEAT.md", "TOOLS.md"],
"skip_dirs": ["node_modules", ".venv", "venv", "__pycache__", ".git"]
}
def load_config() -> Dict[str, Any]:
"""Load cleanup patterns from config file."""
if CONFIG_FILE.exists():
try:
with open(CONFIG_FILE) as f:
return {**DEFAULT_CONFIG, **json.load(f)}
except Exception:
pass
return DEFAULT_CONFIG
def is_recent(path: Path, days: int = 1) -> bool:
"""Check if file was modified recently."""
try:
mtime = datetime.fromtimestamp(path.stat().st_mtime)
return (datetime.now() - mtime).days < days
except Exception:
return True # Error = recent to be safe
def should_protect(path: Path, config: Dict) -> bool:
"""Check if path should be protected from deletion."""
# Protected directories
for protected in config.get("protected_dirs", []):
if protected in path.parts:
return True
# Protected files
if path.name in config.get("protected_files", []):
return True
# Skip dirs
for skip in config.get("skip_dirs", []):
if skip in path.parts:
return True
return False
def find_cruft(workspace: Path, config: Dict, min_age_days: int = 0, min_size_mb: float = 0, include_recent: bool = False) -> List[Dict]:
"""Find files matching cleanup patterns."""
cruft = []
now = datetime.now()
for path in workspace.rglob("*"):
if not path.is_file():
continue
if should_protect(path, config):
continue
if not include_recent and is_recent(path):
continue
# Age filter
if min_age_days > 0:
try:
mtime = datetime.fromtimestamp(path.stat().st_mtime)
if (now - mtime).days < min_age_days:
continue
except Exception:
continue
# Size filter
size_mb = 0
if min_size_mb > 0:
try:
size_mb = path.stat().st_size / (1024 * 1024)
if size_mb < min_size_mb:
continue
except Exception:
continue
# Match patterns
matched = False
reason = ""
# Temp extensions
if path.suffix in config.get("temp_extensions", []):
matched = True
reason = "temp extension"
# Temp patterns
for pattern in config.get("temp_patterns", []):
if pattern in path.name:
matched = True
reason = f"temp pattern '{pattern}'"
break
# Image in wrong location (not in output/images)
if "Workspace_Human" not in path.parts and path.suffix in config.get("image_extensions", []):
matched = True
reason = "image outside output dir"
if matched:
cruft.append({
"path": str(path),
"size_mb": round(size_mb, 2),
"reason": reason
})
return cruft
def move_to_trash(paths: List[str]) -> int:
"""Move files to system trash using trash-put."""
success = 0
for path_str in paths:
path = Path(path_str)
try:
# Try trash-put first
result = subprocess.run(["trash-put", str(path)], capture_output=True)
if result.returncode == 0:
success += 1
print(f"🗑️ Trashed: {path.name}")
else:
# Fallback: move to local trash dir
TRASH_DIR.mkdir(parents=True, exist_ok=True)
dest = TRASH_DIR / path.name
counter = 1
while dest.exists():
dest = TRASH_DIR / f"{path.stem}_{counter}{path.suffix}"
counter += 1
path.rename(dest)
success += 1
print(f"🗑️ Moved to {TRASH_DIR}: {path.name}")
except Exception as e:
print(f"⚠️ Failed: {path.name} - {e}")
return success
def print_report(cruft: List[Dict], dry_run: bool = True):
"""Print cleanup report."""
if not cruft:
print("✅ No cruft found. Workspace is clean!")
return
total_size = sum(c["size_mb"] for c in cruft)
print(f"\n{'='*60}")
print(f"{'🧹 Workspace Cleanup Report':^60}")
print(f"{'='*60}")
print(f"Found {len(cruft)} items ({total_size:.1f} MB)")
print(f"Mode: {'🔍 DRY-RUN (no changes)' if dry_run else '⚠️ WILL DELETE'}\n")
for item in sorted(cruft, key=lambda x: x["size_mb"], reverse=True):
size_str = f"{item['size_mb']:.1f} MB".rjust(10)
print(f" {size_str} {item['path']}")
print(f" → {item['reason']}\n")
print(f"{'='*60}")
if dry_run:
print("💡 Run with --execute to actually delete these files.")
else:
print(f"✅ Successfully trashed {len(cruft)} items.")
def main():
parser = argparse.ArgumentParser(description="Workspace Cleaner - Safe cleanup with dry-run")
parser.add_argument("--workspace", default=str(WORKSPACE), help="Workspace path")
parser.add_argument("--execute", action="store_true", help="Actually delete (default is dry-run)")
parser.add_argument("--min-age", type=int, default=0, help="Only files older than N days")
parser.add_argument("--min-size", type=float, default=0, help="Only files larger than N MB")
parser.add_argument("--include-recent", action="store_true", help="Include files modified in last 24h")
parser.add_argument("--json", action="store_true", help="Output JSON format")
parser.add_argument("--config", help="Custom patterns config file")
args = parser.parse_args()
workspace = Path(args.workspace)
if not workspace.exists():
print(f"❌ Workspace not found: {workspace}")
sys.exit(1)
# Load config
if args.config:
with open(args.config) as f:
config = {**DEFAULT_CONFIG, **json.load(f)}
else:
config = load_config()
# Find cruft
cruft = find_cruft(workspace, config, args.min_age, args.min_size, args.include_recent)
if args.json:
print(json.dumps({"items": cruft, "total": len(cruft), "total_mb": sum(c["size_mb"] for c in cruft)}, indent=2))
return
print_report(cruft, dry_run=not args.execute)
# Execute if requested
if args.execute and cruft:
confirm = input("\n⚠️ Proceed with deletion? (y/N): ")
if confirm.lower() == "y":
moved = move_to_trash([c["path"] for c in cruft])
print(f"\n✅ Done. Trashed {moved}/{len(cruft)} items.")
else:
print("❌ Cancelled.")
if __name__ == "__main__":
main()
FILE:config/patterns.json
{
"temp_extensions": [".tmp", ".bak", ".log", ".skill", ".cache", ".temp"],
"temp_patterns": ["*~", "#*#", ".*.swp", "._*", ".DS_Store", "*.pyc", "__pycache__"],
"image_extensions": [".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg"],
"protected_dirs": ["memory", "skills", "subagents", ".git", "Workspace_Human", "Workspace_Agent", "secret", "archive"],
"protected_files": ["MEMORY.md", "SOUL.md", "USER.md", "AGENTS.md", "HEARTBEAT.md", "TOOLS.md", "IDENTITY.md"],
"skip_dirs": ["node_modules", ".venv", "venv", "__pycache__", ".git", ".github"],
"large_file_threshold_mb": 10,
"archive_age_days": 7
}
FILE:config/sync-config.json
{
"sync_human": true,
"sync_agent": false,
"sync_backup": true,
"output_folder": "AI_Workspace",
"backup_folder": "AI_Workspace_Backup",
"_comment": "Set sync_human/sync_agent to true to enable, false to skip. Requires gog CLI installed and authenticated."
}