用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/majiayu000/claude-skill-registry --skill state-directory-manager命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | state-directory-manager |
| version | 1.0.0 |
| description | Manage persistent state directories for bash scripts |
| author | workspace-hub |
| category | bash |
| tags | ["bash","state","persistence","config","directory","xdg"] |
| platforms | ["linux","macos"] |
Patterns for managing persistent state, configuration, and cache directories in bash scripts following XDG Base Directory specification.
✅ Use when:
❌ Avoid when:
Follow the XDG specification for directory locations:
#!/bin/bash
# ABOUTME: XDG Base Directory compliant state management
# ABOUTME: Cross-platform directory locations
# XDG Base Directories with fallbacks
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
# Application-specific directories
APP_NAME="my-tool"
CONFIG_DIR="$XDG_CONFIG_HOME/$APP_NAME"
DATA_DIR="$XDG_DATA_HOME/$APP_NAME"
STATE_DIR="$XDG_STATE_HOME/$APP_NAME"
CACHE_DIR="$XDG_CACHE_HOME/$APP_NAME"
LOG_DIR="$STATE_DIR/logs"
# Initialize directories
init_directories() {
mkdir -p "$CONFIG_DIR"
mkdir -p "$DATA_DIR"
mkdir -p "$STATE_DIR"
mkdir -p "$CACHE_DIR"
mkdir -p "$LOG_DIR"
}
Alternative using home directory (from workspace-hub scripts):
#!/bin/bash
# ABOUTME: Workspace-hub style state directory management
# ABOUTME: Simple $HOME/.app-name pattern
APP_NAME="workspace-hub"
APP_DIR="${HOME}/.${APP_NAME}"
# Directory structure
CONFIG_DIR="$APP_DIR/config"
DATA_DIR="$APP_DIR/data"
LOGS_DIR="$APP_DIR/logs"
CACHE_DIR="$APP_DIR/cache"
TEMP_DIR="$APP_DIR/tmp"
# Initialize with proper permissions
init_app_dirs() {
local dirs=("$CONFIG_DIR" "$DATA_DIR" "$LOGS_DIR" "$CACHE_DIR" "$TEMP_DIR")
for dir in "${dirs[@]}"; do
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
chmod 700 "$dir" # Private by default
fi
done
}
# Clean old temp files
() {
find - f -mtime +1 -delete 2>/dev/null ||
}
Read and write configuration files:
#!/bin/bash
# ABOUTME: Configuration file management
# ABOUTME: Key-value pairs with defaults
CONFIG_FILE="$CONFIG_DIR/config"
# Default configuration
declare -A DEFAULT_CONFIG=(
["parallel_workers"]="5"
["log_level"]="INFO"
["auto_sync"]="true"
["timeout"]="30"
)
# Initialize config with defaults
init_config() {
if [[ ! -f "$CONFIG_FILE" ]]; then
{
echo "# Configuration for $APP_NAME"
echo "# Generated: $(date)"
echo ""
for key in "${!DEFAULT_CONFIG[@]}"; do
echo "${key}=${DEFAULT_CONFIG[$key]}"
done
} > "$CONFIG_FILE"
fi
}
# Read config value
get_config() {
local key="$1"
local default="${2:-}"
[[ -f ]];
value
value=$(grep 2>/dev/null | -d -f2-)
}
() {
key=
value=
init_config
grep -q 2>/dev/null;
sed -i
>>
}
() {
-gA CONFIG
key ;
CONFIG[]=
[[ -f ]];
IFS= -r key value;
[[ =~ ^#.*$ || -z ]] &&
CONFIG[]=
<
}
init_config
load_config
set_config
Track persistent state between runs:
#!/bin/bash
# ABOUTME: State file operations
# ABOUTME: Track last run, progress, etc.
STATE_FILE="$STATE_DIR/state.json"
# Initialize state
init_state() {
if [[ ! -f "$STATE_FILE" ]]; then
cat > "$STATE_FILE" << EOF
{
"version": "1.0.0",
"created": "$(date -Iseconds)",
"last_run": null,
"run_count": 0,
"last_status": null
}
EOF
fi
}
# Get state value (requires jq)
get_state() {
local key="$1"
local default="${2:-null}"
if [[ -f "$STATE_FILE" ]] && command -v jq &>/dev/null; then
jq -r ".$key // $default" "$STATE_FILE"
else
echo "$default"
fi
}
# Update state value (requires jq)
set_state() {
local key="$1"
local value="$2"
init_state
if -v jq &>/dev/null;
temp=$()
jq > &&
}
() {
status=
set_state
set_state
set_state
}
STATE_KV_FILE=
() {
key=
default=
[[ -f ]];
grep 2>/dev/null | -d -f2- ||
}
() {
key=
value=
-p
[[ -f ]] && grep -q ;
sed -i
>>
}
Implement caching with expiration:
#!/bin/bash
# ABOUTME: Cache management with TTL
# ABOUTME: Store and retrieve cached data
CACHE_TTL="${CACHE_TTL:-3600}" # 1 hour default
# Get cache file path
cache_path() {
local key="$1"
local hash=$(echo -n "$key" | md5sum | cut -c1-16)
echo "$CACHE_DIR/${hash}"
}
# Check if cache is valid
cache_valid() {
local key="$1"
local ttl="${2:-$CACHE_TTL}"
local path=$(cache_path "$key")
if [[ -f "$path" ]]; then
local age=$(($(date +%s) - $(stat -c %Y "$path" 2>/dev/null || stat -f %m "$path")))
[[ $age -lt $ttl ]]
else
return 1
fi
}
# Get from cache
cache_get() {
local key=
ttl=
path=$(cache_path )
cache_valid ;
0
1
}
() {
key=
value=
path=$(cache_path )
-p
>
}
() {
key=
path=$(cache_path )
-f
}
() {
-rf /*
}
() {
ttl=
find - f -mmin -delete 2>/dev/null ||
}
() {
key=
=
ttl=
cache_valid ;
cache_get
result
result=$( )
cache_set
}
result=$(get_with_cache 300)
Manage logs with rotation:
#!/bin/bash
# ABOUTME: Log file management with rotation
# ABOUTME: Automatic cleanup of old logs
LOG_FILE="$LOG_DIR/app.log"
LOG_MAX_SIZE=$((10 * 1024 * 1024)) # 10MB
LOG_MAX_FILES=5
# Initialize logging
init_logging() {
mkdir -p "$LOG_DIR"
touch "$LOG_FILE"
}
# Write to log
log_to_file() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $level: $message" >> "$LOG_FILE"
# Check if rotation needed
maybe_rotate_logs
}
# Rotate logs if needed
maybe_rotate_logs() {
if [[ -f "$LOG_FILE" ]]; then
local size=$(stat -c %s "$LOG_FILE" 2>/dev/null || stat -f %z "$LOG_FILE")
[[ -gt ]];
rotate_logs
}
() {
-f
((i=LOG_MAX_FILES-; i>=; i--));
[[ -f ]];
[[ -f ]];
}
() {
days=
find -name -mtime -delete 2>/dev/null ||
}
() {
lines=
-n
}
() {
pattern=
grep -h /*.* 2>/dev/null | -100
}
#!/bin/bash
# ABOUTME: Complete state directory manager
# ABOUTME: Reusable module for bash scripts
# ─────────────────────────────────────────────────────────────────
# State Directory Manager v1.0.0
# ─────────────────────────────────────────────────────────────────
# Application identity (override in your script)
: "${STATE_APP_NAME:=my-app}"
# Directory setup
STATE_BASE_DIR="${HOME}/.${STATE_APP_NAME}"
STATE_CONFIG_DIR="$STATE_BASE_DIR/config"
STATE_DATA_DIR="$STATE_BASE_DIR/data"
STATE_CACHE_DIR="$STATE_BASE_DIR/cache"
STATE_LOG_DIR="$STATE_BASE_DIR/logs"
STATE_TMP_DIR="$STATE_BASE_DIR/tmp"
# File paths
STATE_CONFIG_FILE="$STATE_CONFIG_DIR/config"
STATE_STATE_FILE="$STATE_DATA_DIR/state"
STATE_LOG_FILE="$STATE_LOG_DIR/app.log"
# Settings
STATE_CACHE_TTL="${STATE_CACHE_TTL:-3600}"
STATE_LOG_MAX_SIZE="${STATE_LOG_MAX_SIZE:-10485760}"
STATE_LOG_MAX_FILES="${STATE_LOG_MAX_FILES:-5}"
# ─────────────────────────────────────────────────────────────────
# Initialization
# ─────────────────────────────────────────────────────────────────
state_init() {
local dirs=(
)
;
[[ ! -d ]];
-p
700
[[ -f ]] ||
[[ -f ]] ||
[[ -f ]] ||
}
() {
key=
default=
grep 2>/dev/null | -d -f2- ||
}
() {
key=
value=
grep -q 2>/dev/null;
sed -i
>>
}
() {
2>/dev/null | grep -v | grep -v
}
() {
key=
default=
grep 2>/dev/null | -d -f2- ||
}
() {
key=
value=
grep -q 2>/dev/null;
sed -i
>>
}
() {
-n | | -c1-16
}
() {
key=
ttl=
path=
[[ -f ]];
age=$(($(date +%s) - $(stat -c %Y "" >/dev/null || stat -f %m "")))
[[ -lt ]];
0
1
}
() {
key=
value=
path=
>
}
() {
-rf /*
}
() {
level=
message=
>>
size=$( -c %s 2>/dev/null || 0)
[[ -gt ]];
state_log_rotate
}
() {
-f
((i=STATE_LOG_MAX_FILES-; i>=; i--));
[[ -f ]] &&
}
() {
-n
}
() {
find - f -mtime +1 -delete 2>/dev/null ||
find - f -mmin -delete 2>/dev/null ||
find -name -mtime +30 -delete 2>/dev/null ||
}
() {
-rf
state_init
}
state_init
#!/bin/bash
# Your script that uses the state manager
# Set app name before sourcing
STATE_APP_NAME="my-tool"
# Source the state manager
source /path/to/state-manager.sh
# Now use it
state_config_set "api_key" "abc123"
api_key=$(state_config_get "api_key")
state_set "last_run" "$(date -Iseconds)"
state_log "INFO" "Script started"
# Use cache
if ! result=$(state_cache_get "api_response"); then
result=$(curl -s https://api.example.com/data)
state_cache_set "api_response" "$result"
fi
$HOME/.app-name