mirror of
https://github.com/tradecatlabs/vibe-coding-cn.git
synced 2026-08-19 05:48:04 +00:00
feat: skills - deepen auto-tmux swarm tooling
This commit is contained in:
@@ -8,7 +8,9 @@
|
||||
scripts/
|
||||
├── AGENTS.md # 本文件:脚本维护规则
|
||||
├── README.md # 面向人类的脚本入口说明
|
||||
└── auto-tmux.sh # 统一 tmux 自动化命令入口
|
||||
├── auto-tmux.sh # 统一 tmux 自动化命令入口
|
||||
├── swarm-state.sh # 蜂群状态、任务、锁和报告管理
|
||||
└── auto-tmux-smoke-test.sh # 临时 tmux 会话端到端自测
|
||||
```
|
||||
|
||||
## 维护规则
|
||||
@@ -17,4 +19,6 @@ scripts/
|
||||
- 发送按键类功能必须先验证目标 pane 存在,并优先打印上下文。
|
||||
- 捕获输出默认脱敏;只有显式 `--no-redact` 才允许输出原文。
|
||||
- 批量救援必须通过 pattern 匹配触发,不允许无条件向所有 pane 发送输入。
|
||||
- 状态脚本只能写入显式状态目录,不保存密钥、Token、密码或私密项目内容。
|
||||
- 自测脚本必须创建独立临时 tmux session,并在退出时清理。
|
||||
- 新增脚本后必须更新 `README.md`、`SKILL.md`、`references/index.md`,并运行 `bash -n` 与 skill strict 校验。
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
## 入口
|
||||
|
||||
- [`auto-tmux.sh`](./auto-tmux.sh) - 统一命令入口,支持拓扑、读取、发送、巡检、救援、录制、等待和 AI 工作台初始化。
|
||||
- [`swarm-state.sh`](./swarm-state.sh) - 蜂群状态、任务、锁和报告管理。
|
||||
- [`auto-tmux-smoke-test.sh`](./auto-tmux-smoke-test.sh) - 创建临时 tmux 会话,端到端验证脚本能力。
|
||||
|
||||
## 常用命令
|
||||
|
||||
@@ -25,6 +27,17 @@ skills/auto-tmux/scripts/auto-tmux.sh send -t "$target" --text "make test" --ent
|
||||
|
||||
# 扫描某个 session 的错误
|
||||
skills/auto-tmux/scripts/auto-tmux.sh scan --session ai-hub --pattern "ERROR|Traceback"
|
||||
|
||||
# 生成证据快照
|
||||
skills/auto-tmux/scripts/auto-tmux.sh snapshot --session ai-hub --dir /tmp/auto-tmux-snapshot
|
||||
|
||||
# 初始化蜂群状态并添加任务
|
||||
skills/auto-tmux/scripts/swarm-state.sh init --dir /tmp/ai_swarm
|
||||
skills/auto-tmux/scripts/swarm-state.sh task-add --id task-001 --text "检查 README 链接"
|
||||
skills/auto-tmux/scripts/swarm-state.sh report --dir /tmp/ai_swarm
|
||||
|
||||
# 端到端自测
|
||||
skills/auto-tmux/scripts/auto-tmux-smoke-test.sh
|
||||
```
|
||||
|
||||
## 安全约束
|
||||
@@ -33,3 +46,4 @@ skills/auto-tmux/scripts/auto-tmux.sh scan --session ai-hub --pattern "ERROR|Tra
|
||||
- `send` 会先打印目标 pane 最近上下文,再发送按键。
|
||||
- 危险命令默认拒绝,必须显式 `--force`。
|
||||
- 批量操作必须使用明确的 session/window/pane 目标。
|
||||
- 多 worker 协作时用 `swarm-state.sh lock-acquire` 声明文件、目录或服务锁。
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke-test auto-tmux scripts without touching user tmux sessions.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
AUTO_TMUX="$SCRIPT_DIR/auto-tmux.sh"
|
||||
SWARM_STATE="$SCRIPT_DIR/swarm-state.sh"
|
||||
SESSION="auto-tmux-smoke-$$"
|
||||
SWARM_DIR="/tmp/auto-tmux-smoke-swarm-$$"
|
||||
SNAPSHOT_DIR="/tmp/auto-tmux-smoke-snapshot-$$"
|
||||
RECORD_DIR="/tmp/auto-tmux-smoke-record-$$"
|
||||
|
||||
cleanup() {
|
||||
tmux kill-session -t "$SESSION" 2>/dev/null || true
|
||||
rm -rf "$SWARM_DIR" "$SNAPSHOT_DIR" "$RECORD_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
command -v tmux >/dev/null 2>&1 || {
|
||||
echo "SKIP: tmux is not installed"
|
||||
exit 0
|
||||
}
|
||||
|
||||
bash -n "$AUTO_TMUX"
|
||||
bash -n "$SWARM_STATE"
|
||||
|
||||
"$AUTO_TMUX" hub --session "$SESSION" --workers 1 --cmd bash
|
||||
"$AUTO_TMUX" topology --session "$SESSION" >/tmp/auto-tmux-smoke-topology.txt
|
||||
|
||||
commander_target="$(tmux list-panes -t "$SESSION:commander" -F '#S:#I.#P' | head -n 1)"
|
||||
worker_target="$(tmux list-panes -t "$SESSION:worker1" -F '#S:#I.#P' | head -n 1)"
|
||||
|
||||
"$AUTO_TMUX" capture -t "$commander_target" -n 10 >/tmp/auto-tmux-smoke-capture.txt
|
||||
"$AUTO_TMUX" send -t "$worker_target" --text "echo AUTO_TMUX_SMOKE_OK" --enter
|
||||
"$AUTO_TMUX" wait -t "$worker_target" --pattern "AUTO_TMUX_SMOKE_OK" --timeout 10 >/tmp/auto-tmux-smoke-wait.txt
|
||||
"$AUTO_TMUX" snapshot --session "$SESSION" --dir "$SNAPSHOT_DIR" -n 20 >/tmp/auto-tmux-smoke-snapshot.txt
|
||||
test -s "$SNAPSHOT_DIR/topology.txt"
|
||||
|
||||
"$AUTO_TMUX" record start -t "$worker_target" --dir "$RECORD_DIR" >/tmp/auto-tmux-smoke-record-start.txt
|
||||
"$AUTO_TMUX" record stop -t "$worker_target" >/tmp/auto-tmux-smoke-record-stop.txt
|
||||
|
||||
"$SWARM_STATE" init --dir "$SWARM_DIR" >/tmp/auto-tmux-smoke-swarm-init.txt
|
||||
"$SWARM_STATE" task-add --dir "$SWARM_DIR" --id smoke-task --text "run smoke task" >/tmp/auto-tmux-smoke-task-add.txt
|
||||
"$SWARM_STATE" task-claim --dir "$SWARM_DIR" --id smoke-task --owner "$worker_target"
|
||||
"$SWARM_STATE" lock-acquire --dir "$SWARM_DIR" --name smoke-file --owner "$worker_target" >/tmp/auto-tmux-smoke-lock.txt
|
||||
"$SWARM_STATE" lock-release --dir "$SWARM_DIR" --name smoke-file --owner "$worker_target" >/tmp/auto-tmux-smoke-unlock.txt
|
||||
"$SWARM_STATE" task-done --dir "$SWARM_DIR" --id smoke-task --owner "$worker_target" --result "ok"
|
||||
"$SWARM_STATE" report --dir "$SWARM_DIR" >/tmp/auto-tmux-smoke-report.txt
|
||||
grep -q 'smoke-task' /tmp/auto-tmux-smoke-report.txt
|
||||
|
||||
printf 'auto-tmux smoke test ok: session=%s worker=%s\n' "$SESSION" "$worker_target"
|
||||
@@ -20,6 +20,7 @@ Usage:
|
||||
auto-tmux.sh scan [--session NAME] [-n LINES] [--pattern REGEX] [--rescue] [--reply TEXT] [--save-dir DIR]
|
||||
auto-tmux.sh record start -t TARGET [--dir DIR]
|
||||
auto-tmux.sh record stop -t TARGET
|
||||
auto-tmux.sh snapshot [--session NAME] [--dir DIR] [-n LINES]
|
||||
auto-tmux.sh hub [--session NAME] [--workers N] [--cmd CMD] [--commander-cmd CMD] [--attach]
|
||||
auto-tmux.sh wait -t TARGET --pattern REGEX [--timeout SEC] [--interval SEC] [-n LINES]
|
||||
|
||||
@@ -32,6 +33,7 @@ Examples:
|
||||
auto-tmux.sh send -t <target-from-topology> --text "make test" --enter
|
||||
auto-tmux.sh rescue -t <target-from-topology> --pattern "(y/n)" --reply y
|
||||
auto-tmux.sh scan --session ai-hub --pattern "ERROR|Traceback"
|
||||
auto-tmux.sh snapshot --session ai-hub --dir /tmp/auto-tmux-snapshot
|
||||
auto-tmux.sh hub --session ai-hub --workers 3 --cmd "codex"
|
||||
EOF
|
||||
}
|
||||
@@ -332,6 +334,39 @@ cmd_record() {
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_snapshot() {
|
||||
local session=""
|
||||
local dir="/tmp/auto-tmux-snapshot-$(date +%Y%m%d-%H%M%S)"
|
||||
local lines=120
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--session) session="${2:-}"; shift 2 ;;
|
||||
--dir) dir="${2:-}"; shift 2 ;;
|
||||
-n|--lines) lines="${2:-}"; shift 2 ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) die "unknown snapshot option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
require_tmux
|
||||
mkdir -p "$dir/panes"
|
||||
{
|
||||
printf 'auto-tmux snapshot\n'
|
||||
printf 'created_at=%s\n' "$(date -Is)"
|
||||
printf 'session=%s\n\n' "${session:-<all>}"
|
||||
cmd_topology ${session:+--session "$session"}
|
||||
} > "$dir/topology.txt"
|
||||
|
||||
local pane safe_name
|
||||
while IFS= read -r pane; do
|
||||
[[ -n "$pane" ]] || continue
|
||||
safe_name="${pane//[:.]/-}.log"
|
||||
capture_print "$pane" "$lines" 0 > "$dir/panes/$safe_name"
|
||||
done < <(pane_list "$session")
|
||||
|
||||
printf 'snapshot written: %s\n' "$dir"
|
||||
}
|
||||
|
||||
cmd_hub() {
|
||||
local session="ai-hub"
|
||||
local workers=3
|
||||
@@ -419,6 +454,7 @@ main() {
|
||||
rescue) cmd_rescue "$@" ;;
|
||||
scan) cmd_scan "$@" ;;
|
||||
record) cmd_record "$@" ;;
|
||||
snapshot) cmd_snapshot "$@" ;;
|
||||
hub) cmd_hub "$@" ;;
|
||||
wait) cmd_wait "$@" ;;
|
||||
*) die "unknown command: $cmd" ;;
|
||||
|
||||
Executable
+303
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env bash
|
||||
# swarm-state: minimal state, task and lock manager for tmux AI swarm workflows.
|
||||
set -euo pipefail
|
||||
|
||||
SWARM_DIR="${AUTO_TMUX_SWARM_DIR:-/tmp/ai_swarm}"
|
||||
STATUS_LOG="$SWARM_DIR/status.log"
|
||||
TASKS_TSV="$SWARM_DIR/tasks.tsv"
|
||||
LOCKS_DIR="$SWARM_DIR/locks"
|
||||
RESULTS_DIR="$SWARM_DIR/results"
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
swarm-state: tmux AI swarm state/task/lock helper
|
||||
|
||||
Usage:
|
||||
swarm-state.sh init [--dir DIR]
|
||||
swarm-state.sh log --target TARGET --status STATUS --message TEXT [--dir DIR]
|
||||
swarm-state.sh status [--dir DIR]
|
||||
swarm-state.sh task-add --text TEXT [--id ID] [--dir DIR]
|
||||
swarm-state.sh task-list [--dir DIR]
|
||||
swarm-state.sh task-claim --id ID --owner TARGET [--dir DIR]
|
||||
swarm-state.sh task-done --id ID --owner TARGET [--result TEXT] [--dir DIR]
|
||||
swarm-state.sh lock-acquire --name NAME --owner TARGET [--dir DIR]
|
||||
swarm-state.sh lock-release --name NAME --owner TARGET [--dir DIR] [--force]
|
||||
swarm-state.sh lock-list [--dir DIR]
|
||||
swarm-state.sh report [--dir DIR]
|
||||
|
||||
Environment:
|
||||
AUTO_TMUX_SWARM_DIR overrides the default /tmp/ai_swarm.
|
||||
EOF
|
||||
}
|
||||
|
||||
die() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sanitize() {
|
||||
tr '\t\r\n' ' ' | sed -E 's/[[:space:]]+/ /g; s/^ //; s/ $//'
|
||||
}
|
||||
|
||||
set_dir() {
|
||||
local dir="$1"
|
||||
SWARM_DIR="$dir"
|
||||
STATUS_LOG="$SWARM_DIR/status.log"
|
||||
TASKS_TSV="$SWARM_DIR/tasks.tsv"
|
||||
LOCKS_DIR="$SWARM_DIR/locks"
|
||||
RESULTS_DIR="$SWARM_DIR/results"
|
||||
}
|
||||
|
||||
ensure_init() {
|
||||
mkdir -p "$LOCKS_DIR" "$RESULTS_DIR"
|
||||
touch "$STATUS_LOG" "$TASKS_TSV"
|
||||
if [[ ! -s "$TASKS_TSV" ]]; then
|
||||
printf 'id\tstatus\towner\ttext\tresult\n' > "$TASKS_TSV"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_init() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
*) die "unknown init option: $1" ;;
|
||||
esac
|
||||
done
|
||||
ensure_init
|
||||
printf 'swarm initialized: %s\n' "$SWARM_DIR"
|
||||
}
|
||||
|
||||
cmd_log() {
|
||||
local target="" status="" message=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--target) target="${2:-}"; shift 2 ;;
|
||||
--status) status="${2:-}"; shift 2 ;;
|
||||
--message) message="${2:-}"; shift 2 ;;
|
||||
*) die "unknown log option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$target" ]] || die "missing --target"
|
||||
[[ -n "$status" ]] || die "missing --status"
|
||||
[[ -n "$message" ]] || die "missing --message"
|
||||
ensure_init
|
||||
message="$(printf '%s' "$message" | sanitize)"
|
||||
printf '[%s] [%s] [%s] %s\n' "$(date +%H:%M:%S)" "$target" "$status" "$message" >> "$STATUS_LOG"
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
local lines=30
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
-n|--lines) lines="${2:-}"; shift 2 ;;
|
||||
*) die "unknown status option: $1" ;;
|
||||
esac
|
||||
done
|
||||
ensure_init
|
||||
tail -n "$lines" "$STATUS_LOG"
|
||||
}
|
||||
|
||||
cmd_task_add() {
|
||||
local id="" text=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--id) id="${2:-}"; shift 2 ;;
|
||||
--text) text="${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-add option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$text" ]] || die "missing --text"
|
||||
ensure_init
|
||||
if [[ -z "$id" ]]; then
|
||||
id="task-$(date +%Y%m%d%H%M%S)-$$"
|
||||
fi
|
||||
text="$(printf '%s' "$text" | sanitize)"
|
||||
printf '%s\tTODO\t-\t%s\t-\n' "$id" "$text" >> "$TASKS_TSV"
|
||||
printf 'task added: %s\n' "$id"
|
||||
}
|
||||
|
||||
cmd_task_list() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-list option: $1" ;;
|
||||
esac
|
||||
done
|
||||
ensure_init
|
||||
column -t -s $'\t' "$TASKS_TSV" 2>/dev/null || cat "$TASKS_TSV"
|
||||
}
|
||||
|
||||
update_task() {
|
||||
local id="$1" status="$2" owner="$3" result="$4"
|
||||
local tmp
|
||||
tmp="$(mktemp)"
|
||||
awk -F '\t' -v OFS='\t' -v id="$id" -v status="$status" -v owner="$owner" -v result="$result" '
|
||||
NR == 1 {print; next}
|
||||
$1 == id {$2 = status; $3 = owner; if (result != "") $5 = result; found = 1}
|
||||
{print}
|
||||
END {if (!found) exit 42}
|
||||
' "$TASKS_TSV" > "$tmp" || {
|
||||
local code=$?
|
||||
rm -f "$tmp"
|
||||
if [[ "$code" == "42" ]]; then
|
||||
die "task not found: $id"
|
||||
fi
|
||||
exit "$code"
|
||||
}
|
||||
mv "$tmp" "$TASKS_TSV"
|
||||
}
|
||||
|
||||
cmd_task_claim() {
|
||||
local id="" owner=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--id) id="${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-claim option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$id" ]] || die "missing --id"
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
ensure_init
|
||||
update_task "$id" "DOING" "$owner" ""
|
||||
cmd_log --target "$owner" --status "START" --message "claimed $id"
|
||||
}
|
||||
|
||||
cmd_task_done() {
|
||||
local id="" owner="" result="done"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--id) id="${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
--result) result="${2:-}"; shift 2 ;;
|
||||
*) die "unknown task-done option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$id" ]] || die "missing --id"
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
ensure_init
|
||||
result="$(printf '%s' "$result" | sanitize)"
|
||||
update_task "$id" "DONE" "$owner" "$result"
|
||||
printf '%s\n' "$result" > "$RESULTS_DIR/$id.txt"
|
||||
cmd_log --target "$owner" --status "DONE" --message "$id $result"
|
||||
}
|
||||
|
||||
lock_path() {
|
||||
local name="$1"
|
||||
name="$(printf '%s' "$name" | sed -E 's#[^A-Za-z0-9_.-]+#_#g')"
|
||||
printf '%s/%s.lock.d' "$LOCKS_DIR" "$name"
|
||||
}
|
||||
|
||||
cmd_lock_acquire() {
|
||||
local name="" owner=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--name) name="${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
*) die "unknown lock-acquire option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$name" ]] || die "missing --name"
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
ensure_init
|
||||
local path
|
||||
path="$(lock_path "$name")"
|
||||
if mkdir "$path" 2>/dev/null; then
|
||||
printf '%s\n' "$owner" > "$path/owner"
|
||||
printf '%s\n' "$(date -Is)" > "$path/created_at"
|
||||
cmd_log --target "$owner" --status "LOCK" --message "acquired $name"
|
||||
printf 'lock acquired: %s\n' "$name"
|
||||
else
|
||||
printf 'lock busy: %s owner=' "$name" >&2
|
||||
cat "$path/owner" >&2 2>/dev/null || printf 'unknown\n' >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_lock_release() {
|
||||
local name="" owner="" force="0"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
--name) name="${2:-}"; shift 2 ;;
|
||||
--owner) owner="${2:-}"; shift 2 ;;
|
||||
--force) force="1"; shift ;;
|
||||
*) die "unknown lock-release option: $1" ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$name" ]] || die "missing --name"
|
||||
[[ -n "$owner" ]] || die "missing --owner"
|
||||
ensure_init
|
||||
local path current_owner
|
||||
path="$(lock_path "$name")"
|
||||
[[ -d "$path" ]] || die "lock not found: $name"
|
||||
current_owner="$(cat "$path/owner" 2>/dev/null || true)"
|
||||
if [[ "$force" != "1" && "$current_owner" != "$owner" ]]; then
|
||||
die "lock owner mismatch: current=$current_owner requested=$owner"
|
||||
fi
|
||||
rm -rf "$path"
|
||||
cmd_log --target "$owner" --status "UNLOCK" --message "released $name"
|
||||
printf 'lock released: %s\n' "$name"
|
||||
}
|
||||
|
||||
cmd_lock_list() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
*) die "unknown lock-list option: $1" ;;
|
||||
esac
|
||||
done
|
||||
ensure_init
|
||||
find "$LOCKS_DIR" -mindepth 1 -maxdepth 1 -type d -name '*.lock.d' -print | sort | while read -r path; do
|
||||
printf '%s\towner=%s\tcreated_at=%s\n' \
|
||||
"$(basename "$path" .lock.d)" \
|
||||
"$(cat "$path/owner" 2>/dev/null || printf unknown)" \
|
||||
"$(cat "$path/created_at" 2>/dev/null || printf unknown)"
|
||||
done
|
||||
}
|
||||
|
||||
cmd_report() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dir) set_dir "${2:-}"; shift 2 ;;
|
||||
*) die "unknown report option: $1" ;;
|
||||
esac
|
||||
done
|
||||
ensure_init
|
||||
printf '# swarm report\n\n'
|
||||
printf 'dir: %s\n\n' "$SWARM_DIR"
|
||||
printf '## tasks\n\n'
|
||||
cmd_task_list
|
||||
printf '\n## locks\n\n'
|
||||
cmd_lock_list || true
|
||||
printf '\n## recent status\n\n'
|
||||
cmd_status -n 20
|
||||
}
|
||||
|
||||
main() {
|
||||
local cmd="${1:-help}"
|
||||
shift || true
|
||||
case "$cmd" in
|
||||
help|-h|--help) usage ;;
|
||||
init) cmd_init "$@" ;;
|
||||
log) cmd_log "$@" ;;
|
||||
status) cmd_status "$@" ;;
|
||||
task-add) cmd_task_add "$@" ;;
|
||||
task-list) cmd_task_list "$@" ;;
|
||||
task-claim) cmd_task_claim "$@" ;;
|
||||
task-done) cmd_task_done "$@" ;;
|
||||
lock-acquire) cmd_lock_acquire "$@" ;;
|
||||
lock-release) cmd_lock_release "$@" ;;
|
||||
lock-list) cmd_lock_list "$@" ;;
|
||||
report) cmd_report "$@" ;;
|
||||
*) die "unknown command: $cmd" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user