SuperAPI AI Directory

tmux-serv

General & other

View repository

Updated 10-15 21:52

About this skill

tmux-serv

Use one executable ~/.tmux.services to manage services across projects; each project only maintains its own foreground launcher.

~/.tmux.services                         # 全局清单与 tmux 生命周期
~/dev/project-a/tools/start_ocr_server.sh
~/dev/project-b/tools/start_chat_server.sh

When the service name is globally unique, directly use <name>, with session as serv-<name>; only when there is a name conflict use <project>/<name>, with session as serv-<project>-<name>。

Dependencies

Requires Bash 3.2+ and tmux:

brew install tmux       # macOS
# apt install tmux      # Debian / Ubuntu

Install the global script

First record this skill's absolute directory as SKILL_ROOT:

SKILL_ROOT="<tmux-serv skill 的绝对路径>"
bash "$SKILL_ROOT/scripts/install.sh"

On first install, write ~/.tmux.services and set it to 0700。If the target already exists, the installer refuses to overwrite。

For isolated/test scenarios, you can specify a custom target path, without writing to ~/.tmux.services; if the target already exists, it likewise refuses to overwrite:

bash "$SKILL_ROOT/scripts/install.sh" --target /path/to/.tmux.services

Upgrade template:

bash "$SKILL_ROOT/scripts/install.sh" --upgrade

Upgrading keeps only the user service section between the following markers, replaces the rest of the engine with the new version, and first creates a timestamped .bak backup:

# >>> tmux-serv user services >>>
# <<< tmux-serv user services <<<

If markers are missing, duplicated, or the merged result fails validation, the installer refuses to modify. The installer only cleans up its own temporary merge files and does not delete user configuration or backups. Do not manually modify the engine outside the markers; complex customizations should keep their own fork.

Project launcher contract

One in-project script per service, placed directly in the project root's tools/ :

#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT_DIR"

exec env PORT="${API_PORT:-3000}" node server.js

Python service example:

#!/usr/bin/env bash
set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT_DIR"

# 优先用项目内 venv,没有则回落到系统 python
if [[ -x ".venv/bin/python" ]]; then
    PY=".venv/bin/python"
else
    PY="python3"
fi

exec env \
    PORT="${API_PORT:-8000}" \
    HOST="${API_HOST:-127.0.0.1}" \
    "$PY" -m app.main

If it is a uv-managed project, it can be simplified to:

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT_DIR"
exec env PORT="${API_PORT:-8000}" uv run python -m app.main

Must satisfy:

  1. The file is located at <project>/tools/, exists, and is executable
  2. Locate the project from its own path, not depending on the caller's cwd
  3. Do not create tmux, do not use nohup, trailing & , or self-daemonize
  4. Ultimately exec the foreground main process; dependencies, ports, parameters, and environment defaults are maintained by the project
  5. On startup failure, write to stderr and return non-zero; the script should be directly executable from any cwd
  6. Do not write secrets into ~/.tmux.services; provide them via the project's secure configuration or process environment

Register a service

Only edit the user service section of ~/.tmux.services :

configure_services() {
    start_service "project-a" "ocr" \
        "$HOME/dev/project-a/tools/start_ocr_server.sh"

    start_service "project-b" "chat-room" \
        "$HOME/dev/project-b/tools/start_chat_server.sh"
}

start_service receives exactly three parameters:

Parameter Rule
project 1–40 lowercase letters, digits, single hyphens; leading/trailing hyphens and --
service Same as above; only needs to be unique within the project
launcher Absolute path or ~/ path; spaces allowed, must be directly located in the project tools/

project/service The composite key must be unique. If service is unique in the entire table, the CLI directly uses service, with session as serv-<name>; if there is a name conflict, then project/servicemust be used, with session as serv-<project>-<name>. Adding a second service with the same name will change the derived session of the original service; the old serv-<name> will not be stopped automatically; you should first confirm ownership and stop precisely, then start the new session item by item. The registry is executable Shell; only install and edit content you trust; calls including --check、--status will execute the configuration section to load entries, but only startup or restart executes the launcher.

After registration, validate first without starting the service:

~/.tmux.services --check

Daily operations

# 仅补启动全部缺失服务;已有 session 不动
~/.tmux.services

# service 唯一时直接使用短名
~/.tmux.services chat-room
~/.tmux.services --restart chat-room

# 只有重名时才带项目名
~/.tmux.services project-a/api
~/.tmux.services --restart project-a/api

# 明确要求时才重启全部;非事务操作,失败不会回滚前项
~/.tmux.services --restart-all

# 查看全部或单项状态
~/.tmux.services --status
~/.tmux.services --status chat-room

# 最近 200 行、附着、精确停止
~/.tmux.services --logs chat-room
~/.tmux.services --attach chat-room
~/.tmux.services --stop chat-room

All existence checks, stops, and operation targets use tmux's =<session> Exact match. If the session disappears within the 1-second stabilization window after startup, the command returns 70 and prompts you to run the launcher directly for troubleshooting; this is not a port or HTTP health check.

Safe default

  • With no arguments, only fills in missing services at startup; does not restart existing services
  • Only an explicit --restart-all triggers a full restart
  • Does not source external configuration, does not eval service commands; the main script directly executes the registered launcher
  • The launcher path is physically normalized; symlinks, relative paths, and tools/ external entries are rejected
  • --status Only lists registered services; does not scan or operate on other serv- session
  • Test-only TMUX_SERV_SOCKET_NAME can switch to an independent tmux server; do not set it for daily use. This variable must be set before calling ~/.tmux.services before export; writing it to the config file has no effect; all tmux acceptance commands must include -L "$TMUX_SERV_SOCKET_NAME" to hit the isolated server

Exit code

Exit code Meaning
0 Operation completed, including "already running/not running so skipped"
64 Argument, name, or registration configuration error
66 launcher missing, not executable, or path non-compliant
69 tmux missing, or log/attach target not running
70 tmux start/stop failed, or the service exited immediately after startup

--restart-all Executes item by item and is not transactional; if earlier items succeed and later items fail, there is no rollback.

Acceptance

bash -n ~/.tmux.services
~/.tmux.services --check
~/.tmux.services <name>
tmux has-session -t '=serv-<name>'
tmux display-message -p -t '=serv-<name>:' '#{pane_current_path}'
~/.tmux.services --status <name>

# 仅重名服务改用 project/service 与 serv-<project>-<name>
~/.tmux.services <project>/<name>
tmux has-session -t '=serv-<project>-<name>'

Isolated test

Test-only; no need to set for daily use

export TMUX_SERV_SOCKET_NAME=tmuxserv-sandbox
~/.tmux.services <name>
tmux -L "$TMUX_SERV_SOCKET_NAME" has-session -t '=serv-<name>'
tmux -L "$TMUX_SERV_SOCKET_NAME" display-message -p -t '=serv-<name>:' '#{pane_current_path}'
~/.tmux.services --status <name>
unset TMUX_SERV_SOCKET_NAME

Then check ports, health endpoints, and logs according to the service protocol.