Commit 557ea847 authored by Andrey Filippov's avatar Andrey Filippov
Browse files

Working on global LMA pose fitting and autonomous program run by the

agent.
parent 85eab198
Loading
Loading
Loading
Loading
+135 −0
Original line number Diff line number Diff line
@@ -21,6 +21,12 @@ HTTP MCP endpoints and usage notes for controlling imagej-elphel via MCP.

Path: `scripts/mcp-http-howto.md`

### eyesis-host-daemon-howto.md

Host daemon workflow that removes repeated approval prompts by using a local file queue.

Path: `scripts/eyesis-host-daemon-howto.md`

### email_send.py

Send email via SSH+sendmail on community.elphel.com by default (set ELPHEL_SEND_MODE=smtp to force SMTP).
@@ -185,3 +191,132 @@ Dependencies:
- rag venv

Tags: rag, query, wrapper

## eyesis_mcp_ctl.sh

Lifecycle controller for Eyesis_Correction in MCP mode (start/stop/restart/status/logs/wait).

Path: `scripts/eyesis_mcp_ctl.sh`

Example:

```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_mcp_ctl.sh start --configdir /media/elphel/btrfs-data/lwir16-proc/NC/config --rebuild
```

Inputs:
- command: start|stop|restart|status|logs|wait
- optional flags: --configdir/--config/--port/--rebuild/--log
- environment overrides: EYESIS_MCP_*

Outputs:
- Background JVM process managed by pid file
- MCP readiness checks and status output
- Log file at attic/session-logs/eyesis_mcp.log by default

Dependencies:
- mvn
- curl

Tags: mcp, lifecycle, launcher, automation

## mcp_http.sh

Thin MCP HTTP wrapper for status/dialog/button/value/submit/interrupt operations.

Path: `scripts/mcp_http.sh`

Example:

```bash
/home/elphel/git/imagej-elphel/scripts/mcp_http.sh button --label "Restore"
```

Inputs:
- subcommand: status|dialog|button|set|submit|interrupt|confirm-stop
- subcommand flags like --label/--value/--id/--ok
- optional --base and --timeout (or EYESIS_MCP_BASE_URL/EYESIS_MCP_TIMEOUT_SEC)

Outputs:
- Raw JSON response from MCP HTTP endpoint

Dependencies:
- curl

Tags: mcp, http, automation, wrapper

## eyesis_host_daemon.py

Host-side queue daemon that executes whitelisted lifecycle/MCP wrapper commands in desktop session context.

Path: `scripts/eyesis_host_daemon.py`

Example:

```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon.py submit --action ctl -- status
```

Inputs:
- serve mode with queue dir and poll interval
- submit mode: action ctl|mcp and args after --
- read mode: request id

Outputs:
- JSON response files in queue responses/
- stdout/stderr passthrough for submit --wait-sec

Dependencies:
- python3

Tags: daemon, queue, automation, mcp, lifecycle

## eyesis_host_daemon_ctl.sh

Start/stop/status/logs helper for eyesis_host_daemon.py.

Path: `scripts/eyesis_host_daemon_ctl.sh`

Example:

```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh start
```

Inputs:
- command: start|stop|restart|status|logs
- optional flags: --queue-dir/--log/--poll-ms/--force

Outputs:
- Background daemon process and PID file
- Daemon status and log streaming

Dependencies:
- python3

Tags: daemon, control, automation

## eyesis_host_submit.sh

Convenience wrapper to submit daemon requests for ctl/mcp actions.

Path: `scripts/eyesis_host_submit.sh`

Example:

```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh mcp status
```

Inputs:
- action: ctl or mcp
- subcommand arguments for selected action

Outputs:
- Command output from daemon response

Dependencies:
- python3
- eyesis_host_daemon.py

Tags: daemon, client, automation, mcp, lifecycle
+109 −0
Original line number Diff line number Diff line
@@ -6,6 +6,11 @@
      "path": "scripts/mcp-http-howto.md",
      "purpose": "HTTP MCP endpoints and usage notes for controlling imagej-elphel via MCP."
    },
    {
      "name": "eyesis-host-daemon-howto.md",
      "path": "scripts/eyesis-host-daemon-howto.md",
      "purpose": "Host daemon workflow that removes repeated approval prompts by using a local file queue."
    },
    {
      "name": "email_send.py",
      "path": "scripts/email_send.py",
@@ -136,6 +141,110 @@
      ],
      "owner": "codex",
      "created": "2026-02-04"
    },
    {
      "name": "eyesis_mcp_ctl.sh",
      "path": "scripts/eyesis_mcp_ctl.sh",
      "purpose": "Lifecycle controller for Eyesis_Correction in MCP mode (start/stop/restart/status/logs/wait).",
      "inputs": [
        "command: start|stop|restart|status|logs|wait",
        "optional flags: --configdir/--config/--port/--rebuild/--log",
        "environment overrides: EYESIS_MCP_*"
      ],
      "outputs": [
        "Background JVM process managed by pid file",
        "MCP readiness checks and status output",
        "Log file at attic/session-logs/eyesis_mcp.log by default"
      ],
      "example": "/home/elphel/git/imagej-elphel/scripts/eyesis_mcp_ctl.sh start --configdir /media/elphel/btrfs-data/lwir16-proc/NC/config --rebuild",
      "tags": ["mcp", "lifecycle", "launcher", "automation"],
      "dependencies": [
        "mvn",
        "curl"
      ],
      "owner": "codex",
      "created": "2026-02-23"
    },
    {
      "name": "mcp_http.sh",
      "path": "scripts/mcp_http.sh",
      "purpose": "Thin MCP HTTP wrapper for status/dialog/button/value/submit/interrupt operations.",
      "inputs": [
        "subcommand: status|dialog|button|set|submit|interrupt|confirm-stop",
        "subcommand flags like --label/--value/--id/--ok",
        "optional --base and --timeout (or EYESIS_MCP_BASE_URL/EYESIS_MCP_TIMEOUT_SEC)"
      ],
      "outputs": [
        "Raw JSON response from MCP HTTP endpoint"
      ],
      "example": "/home/elphel/git/imagej-elphel/scripts/mcp_http.sh button --label \"Restore\"",
      "tags": ["mcp", "http", "automation", "wrapper"],
      "dependencies": [
        "curl"
      ],
      "owner": "codex",
      "created": "2026-02-23"
    },
    {
      "name": "eyesis_host_daemon.py",
      "path": "scripts/eyesis_host_daemon.py",
      "purpose": "Host-side queue daemon that executes whitelisted lifecycle/MCP wrapper commands in desktop session context.",
      "inputs": [
        "serve mode with queue dir and poll interval",
        "submit mode: action ctl|mcp and args after --",
        "read mode: request id"
      ],
      "outputs": [
        "JSON response files in queue responses/",
        "stdout/stderr passthrough for submit --wait-sec"
      ],
      "example": "/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon.py submit --action ctl -- status",
      "tags": ["daemon", "queue", "automation", "mcp", "lifecycle"],
      "dependencies": [
        "python3"
      ],
      "owner": "codex",
      "created": "2026-02-23"
    },
    {
      "name": "eyesis_host_daemon_ctl.sh",
      "path": "scripts/eyesis_host_daemon_ctl.sh",
      "purpose": "Start/stop/status/logs helper for eyesis_host_daemon.py.",
      "inputs": [
        "command: start|stop|restart|status|logs",
        "optional flags: --queue-dir/--log/--poll-ms/--force"
      ],
      "outputs": [
        "Background daemon process and PID file",
        "Daemon status and log streaming"
      ],
      "example": "/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh start",
      "tags": ["daemon", "control", "automation"],
      "dependencies": [
        "python3"
      ],
      "owner": "codex",
      "created": "2026-02-23"
    },
    {
      "name": "eyesis_host_submit.sh",
      "path": "scripts/eyesis_host_submit.sh",
      "purpose": "Convenience wrapper to submit daemon requests for ctl/mcp actions.",
      "inputs": [
        "action: ctl or mcp",
        "subcommand arguments for selected action"
      ],
      "outputs": [
        "Command output from daemon response"
      ],
      "example": "/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh mcp status",
      "tags": ["daemon", "client", "automation", "mcp", "lifecycle"],
      "dependencies": [
        "python3",
        "eyesis_host_daemon.py"
      ],
      "owner": "codex",
      "created": "2026-02-23"
    }
  ]
}
+86 −0
Original line number Diff line number Diff line
# Eyesis Host Daemon HOWTO

This daemon avoids repeated Codex approval prompts by running host-side command execution in your desktop session.
Codex then submits requests through a file queue in the workspace.

## Why use it
- No per-command approval prompts after daemon is started.
- Works across machines with one-time startup.
- Keeps command scope restricted to existing wrappers:
  - `scripts/eyesis_mcp_ctl.sh`
  - `scripts/mcp_http.sh`

## Components
- `scripts/eyesis_host_daemon.py`:
  - `serve`: daemon loop
  - `submit`: enqueue a request and optionally wait for response
- `scripts/eyesis_host_daemon_ctl.sh`:
  - start/stop/restart/status/logs for daemon itself
- `scripts/eyesis_host_submit.sh`:
  - convenience wrapper around `submit`

## One-time start (per login)
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh start
```

Check status:
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh status
```

View daemon log:
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh logs --lines 120
```

Real-time tails in a separate console:
```bash
tail -n 200 -F /home/elphel/git/imagej-elphel/attic/session-logs/eyesis_mcp.log
tail -n 100 -F /home/elphel/git/imagej-elphel/attic/session-logs/eyesis_host_daemon.log
```

## Request examples
Daemon-backed lifecycle request:
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh ctl status
/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh ctl start --configdir /media/elphel/btrfs-data/lwir16-proc/NC/config --wait-sec 120
```

Daemon-backed MCP request:
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh mcp status
/home/elphel/git/imagej-elphel/scripts/eyesis_host_submit.sh mcp button --label "Restore"
```

## Queue and logs
Default queue directory:
- `attic/session-logs/eyesis-host-daemon/`

Subdirectories:
- `requests/` incoming jobs
- `processing/` in-progress jobs
- `responses/` completed results
- `archive/` processed request files

Daemon log:
- `attic/session-logs/eyesis_host_daemon.log`

Eyesis console log (from lifecycle script):
- `attic/session-logs/eyesis_mcp.log`

## Stop daemon
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh stop
```

Force stop if needed:
```bash
/home/elphel/git/imagej-elphel/scripts/eyesis_host_daemon_ctl.sh stop --force
```

## New computer setup
1. Copy/clone repo.
2. Build once (`mvn -DskipTests clean package`) from your normal user session.
3. Start daemon (`eyesis_host_daemon_ctl.sh start`).
4. Use `eyesis_host_submit.sh` for run control.
+277 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
"""
Host-side Eyesis automation daemon.

Purpose:
- Run this daemon once in the user's desktop session.
- Codex (sandboxed) submits JSON requests via files in a shared queue dir.
- Daemon executes a restricted command set on the host (GUI/session-visible context).

Usage:
  scripts/eyesis_host_daemon.py serve [--queue-dir DIR] [--poll-ms 200]
  scripts/eyesis_host_daemon.py submit --action {ctl,mcp} -- <args...> [--queue-dir DIR] [--wait-sec 120]
  scripts/eyesis_host_daemon.py read --id REQUEST_ID [--queue-dir DIR]

Queue layout (default): attic/session-logs/eyesis-host-daemon/
  requests/*.json
  processing/*.json
  responses/*.json
  archive/*.json
"""

from __future__ import annotations

import argparse
import datetime as dt
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import time
import uuid

REPO_ROOT = Path(__file__).resolve().parents[1]
DEFAULT_QUEUE = REPO_ROOT / "attic" / "session-logs" / "eyesis-host-daemon"
CTL_SCRIPT = REPO_ROOT / "scripts" / "eyesis_mcp_ctl.sh"
MCP_SCRIPT = REPO_ROOT / "scripts" / "mcp_http.sh"

ALLOWED_CTL = {"start", "stop", "restart", "status", "wait", "logs"}
ALLOWED_MCP = {"status", "dialog", "button", "set", "submit", "interrupt", "confirm-stop"}


def now_iso() -> str:
    return dt.datetime.now(dt.timezone.utc).isoformat()


def ensure_dirs(base: Path) -> dict[str, Path]:
    dirs = {
        "base": base,
        "requests": base / "requests",
        "processing": base / "processing",
        "responses": base / "responses",
        "archive": base / "archive",
    }
    for p in dirs.values():
        p.mkdir(parents=True, exist_ok=True)
    return dirs


def atomic_write_json(path: Path, payload: dict) -> None:
    tmp = path.with_suffix(path.suffix + ".tmp")
    tmp.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    os.replace(tmp, path)


def validate_request(req: dict) -> tuple[bool, str]:
    action = req.get("action")
    args = req.get("args")
    if action not in {"ctl", "mcp"}:
        return False, "action must be 'ctl' or 'mcp'"
    if not isinstance(args, list) or not all(isinstance(x, str) for x in args):
        return False, "args must be a list of strings"
    if not args:
        return False, "args must not be empty"

    first = args[0]
    if action == "ctl" and first not in ALLOWED_CTL:
        return False, f"unsupported ctl subcommand: {first}"
    if action == "mcp" and first not in ALLOWED_MCP:
        return False, f"unsupported mcp subcommand: {first}"
    return True, ""


def build_command(action: str, args: list[str]) -> list[str]:
    if action == "ctl":
        return [str(CTL_SCRIPT), *args]
    return [str(MCP_SCRIPT), *args]


def process_one(req_path: Path, dirs: dict[str, Path]) -> None:
    proc_path = dirs["processing"] / req_path.name
    os.replace(req_path, proc_path)

    req_id = req_path.stem
    started = now_iso()
    result: dict = {
        "id": req_id,
        "started_at": started,
    }

    try:
        req = json.loads(proc_path.read_text(encoding="utf-8"))
        result["request"] = req
        ok, err = validate_request(req)
        if not ok:
            result.update({"ok": False, "exit_code": 2, "error": err, "stdout": "", "stderr": ""})
        else:
            timeout = req.get("timeout_sec", 300)
            if not isinstance(timeout, int) or timeout < 1 or timeout > 3600:
                timeout = 300
            cmd = build_command(req["action"], req["args"])
            cp = subprocess.run(
                cmd,
                cwd=str(REPO_ROOT),
                text=True,
                capture_output=True,
                timeout=timeout,
                check=False,
            )
            result.update(
                {
                    "ok": cp.returncode == 0,
                    "exit_code": cp.returncode,
                    "stdout": cp.stdout,
                    "stderr": cp.stderr,
                    "command": cmd,
                }
            )
    except subprocess.TimeoutExpired as e:
        result.update(
            {
                "ok": False,
                "exit_code": 124,
                "error": f"timeout after {e.timeout}s",
                "stdout": e.stdout or "",
                "stderr": e.stderr or "",
            }
        )
    except Exception as e:
        result.update(
            {
                "ok": False,
                "exit_code": 1,
                "error": f"daemon exception: {e}",
                "stdout": "",
                "stderr": "",
            }
        )
    finally:
        result["finished_at"] = now_iso()

    resp_path = dirs["responses"] / f"{req_id}.json"
    atomic_write_json(resp_path, result)
    shutil.move(str(proc_path), str(dirs["archive"] / proc_path.name))


def cmd_serve(args: argparse.Namespace) -> int:
    dirs = ensure_dirs(Path(args.queue_dir).resolve())
    poll_s = max(0.05, args.poll_ms / 1000.0)

    print(f"eyesis-host-daemon: serving {dirs['base']}")
    print(f"eyesis-host-daemon: ctl script={CTL_SCRIPT}")
    print(f"eyesis-host-daemon: mcp script={MCP_SCRIPT}")
    sys.stdout.flush()

    while True:
        reqs = sorted(dirs["requests"].glob("*.json"))
        if not reqs:
            time.sleep(poll_s)
            continue
        for req in reqs:
            process_one(req, dirs)


def submit_request(queue_dir: Path, action: str, req_args: list[str], timeout_sec: int) -> str:
    dirs = ensure_dirs(queue_dir)
    req_id = uuid.uuid4().hex
    payload = {
        "id": req_id,
        "created_at": now_iso(),
        "action": action,
        "args": req_args,
        "timeout_sec": timeout_sec,
        "cwd": str(REPO_ROOT),
    }
    req_path = dirs["requests"] / f"{req_id}.json"
    atomic_write_json(req_path, payload)
    return req_id


def wait_response(queue_dir: Path, req_id: str, wait_sec: int) -> dict | None:
    resp_path = queue_dir / "responses" / f"{req_id}.json"
    deadline = time.time() + max(1, wait_sec)
    while time.time() < deadline:
        if resp_path.exists():
            return json.loads(resp_path.read_text(encoding="utf-8"))
        time.sleep(0.1)
    return None


def cmd_submit(args: argparse.Namespace) -> int:
    queue_dir = Path(args.queue_dir).resolve()
    req_id = submit_request(queue_dir, args.action, args.req_args, args.timeout_sec)
    print(req_id)
    if args.wait_sec <= 0:
        return 0

    resp = wait_response(queue_dir, req_id, args.wait_sec)
    if resp is None:
        print(json.dumps({"ok": False, "id": req_id, "error": "timeout waiting response"}, indent=2))
        return 124

    # print command outputs in a terminal-friendly way
    stdout = resp.get("stdout", "")
    stderr = resp.get("stderr", "")
    if stdout:
        sys.stdout.write(stdout)
        if not stdout.endswith("\n"):
            sys.stdout.write("\n")
    if stderr:
        sys.stderr.write(stderr)
        if not stderr.endswith("\n"):
            sys.stderr.write("\n")
    return int(resp.get("exit_code", 1))


def cmd_read(args: argparse.Namespace) -> int:
    queue_dir = Path(args.queue_dir).resolve()
    resp_path = queue_dir / "responses" / f"{args.id}.json"
    if not resp_path.exists():
        print(f"response not found: {resp_path}", file=sys.stderr)
        return 1
    print(resp_path.read_text(encoding="utf-8"), end="")
    return 0


def build_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(description="Eyesis host daemon")
    sub = p.add_subparsers(dest="cmd", required=True)

    p_serve = sub.add_parser("serve", help="Run daemon loop")
    p_serve.add_argument("--queue-dir", default=str(DEFAULT_QUEUE))
    p_serve.add_argument("--poll-ms", type=int, default=200)
    p_serve.set_defaults(func=cmd_serve)

    p_submit = sub.add_parser("submit", help="Submit one request")
    p_submit.add_argument("--queue-dir", default=str(DEFAULT_QUEUE))
    p_submit.add_argument("--action", choices=["ctl", "mcp"], required=True)
    p_submit.add_argument("--wait-sec", type=int, default=120)
    p_submit.add_argument("--timeout-sec", type=int, default=300)
    p_submit.add_argument("req_args", nargs=argparse.REMAINDER, help="Request args after '--'")
    p_submit.set_defaults(func=cmd_submit)

    p_read = sub.add_parser("read", help="Read response by id")
    p_read.add_argument("--queue-dir", default=str(DEFAULT_QUEUE))
    p_read.add_argument("--id", required=True)
    p_read.set_defaults(func=cmd_read)
    return p


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()

    if getattr(args, "cmd", "") == "submit":
        if args.req_args and args.req_args[0] == "--":
            args.req_args = args.req_args[1:]
        if not args.req_args:
            print("submit requires request args after '--'", file=sys.stderr)
            return 2

    return args.func(args)


if __name__ == "__main__":
    raise SystemExit(main())
+141 −0
Original line number Diff line number Diff line
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
DAEMON="${REPO_ROOT}/scripts/eyesis_host_daemon.py"

QUEUE_DIR="${EYESIS_HOST_QUEUE_DIR:-${REPO_ROOT}/attic/session-logs/eyesis-host-daemon}"
STATE_DIR="${QUEUE_DIR}"
PID_FILE="${STATE_DIR}/daemon.pid"
LOG_FILE="${EYESIS_HOST_DAEMON_LOG:-${REPO_ROOT}/attic/session-logs/eyesis_host_daemon.log}"
POLL_MS="${EYESIS_HOST_DAEMON_POLL_MS:-200}"
FORCE=0
LINES=80

usage() {
  cat <<'USAGE'
Usage:
  scripts/eyesis_host_daemon_ctl.sh start [--queue-dir DIR] [--log FILE] [--poll-ms N]
  scripts/eyesis_host_daemon_ctl.sh stop [--force] [--queue-dir DIR]
  scripts/eyesis_host_daemon_ctl.sh restart [options]
  scripts/eyesis_host_daemon_ctl.sh status [--queue-dir DIR]
  scripts/eyesis_host_daemon_ctl.sh logs [--lines N] [--log FILE]

Environment overrides:
  EYESIS_HOST_QUEUE_DIR, EYESIS_HOST_DAEMON_LOG, EYESIS_HOST_DAEMON_POLL_MS
USAGE
}

is_running() {
  [[ -f "${PID_FILE}" ]] || return 1
  local pid
  pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
  [[ -n "${pid}" ]] || return 1
  kill -0 "${pid}" 2>/dev/null
}

clear_stale() {
  if [[ -f "${PID_FILE}" ]] && ! is_running; then
    rm -f "${PID_FILE}"
  fi
}

start_daemon() {
  mkdir -p "${QUEUE_DIR}" "${REPO_ROOT}/attic/session-logs"
  clear_stale
  if is_running; then
    echo "daemon: running (pid=$(cat "${PID_FILE}"))"
    return 0
  fi
  nohup "${DAEMON}" serve --queue-dir "${QUEUE_DIR}" --poll-ms "${POLL_MS}" >>"${LOG_FILE}" 2>&1 &
  echo $! >"${PID_FILE}"
  sleep 0.3
  if is_running; then
    echo "daemon: started (pid=$(cat "${PID_FILE}"))"
    echo "queue: ${QUEUE_DIR}"
    echo "log: ${LOG_FILE}"
    return 0
  fi
  echo "daemon: failed to start" >&2
  return 1
}

stop_daemon() {
  clear_stale
  if ! is_running; then
    echo "daemon: stopped"
    return 0
  fi
  local pid
  pid="$(cat "${PID_FILE}")"
  kill -TERM "${pid}" 2>/dev/null || true
  for _ in $(seq 1 20); do
    if ! kill -0 "${pid}" 2>/dev/null; then
      rm -f "${PID_FILE}"
      echo "daemon: stopped"
      return 0
    fi
    sleep 0.2
  done
  if [[ "${FORCE}" -eq 1 ]]; then
    kill -KILL "${pid}" 2>/dev/null || true
    rm -f "${PID_FILE}"
    echo "daemon: force-stopped"
    return 0
  fi
  echo "daemon: stop timeout; use --force" >&2
  return 1
}

status_daemon() {
  clear_stale
  if is_running; then
    echo "daemon: running (pid=$(cat "${PID_FILE}"))"
  else
    echo "daemon: stopped"
  fi
  echo "queue: ${QUEUE_DIR}"
  echo "log: ${LOG_FILE}"
}

show_logs() {
  [[ -f "${LOG_FILE}" ]] || { echo "log file not found: ${LOG_FILE}" >&2; return 1; }
  tail -n "${LINES}" -f "${LOG_FILE}"
}

[[ $# -ge 1 ]] || { usage; exit 2; }
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
  usage
  exit 0
fi
cmd="$1"
shift

while [[ $# -gt 0 ]]; do
  case "$1" in
    --queue-dir)
      QUEUE_DIR="$2"; STATE_DIR="$2"; PID_FILE="${STATE_DIR}/daemon.pid"; shift 2 ;;
    --log)
      LOG_FILE="$2"; shift 2 ;;
    --poll-ms)
      POLL_MS="$2"; shift 2 ;;
    --force)
      FORCE=1; shift ;;
    --lines)
      LINES="$2"; shift 2 ;;
    -h|--help)
      usage; exit 0 ;;
    *)
      echo "Unknown option: $1" >&2; usage; exit 2 ;;
  esac
 done

case "${cmd}" in
  start) start_daemon ;;
  stop) stop_daemon ;;
  restart) stop_daemon || true; start_daemon ;;
  status) status_daemon ;;
  logs) show_logs ;;
  *) echo "Unknown command: ${cmd}" >&2; usage; exit 2 ;;
esac
Loading