claude-controller 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 choiwon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # Controller
2
+
3
+ A shell wrapper that runs Claude Code CLI as a headless daemon. Provides FIFO pipe-based async task dispatch, Git Worktree isolation, automatic checkpointing/rewind, and a web dashboard for remote task management.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────┐
9
+ │ Web Dashboard (Vanilla JS) │
10
+ │ https://claude.won-space.com <-> localhost:8420 │
11
+ └────────────────────┬────────────────────────────────────────────┘
12
+ │ REST API (Python http.server)
13
+ ┌────────────────────▼────────────────────────────────────────────┐
14
+ │ Web Server (native-app.py) │
15
+ │ ┌──────────┐ ┌────────────┐ ┌──────────┐ ┌───────────────────┐│
16
+ │ │ handler │ │ jobs.py │ │ auth.py │ │ checkpoint.py ││
17
+ │ │ (REST) │ │ (FIFO I/O) │ │ (Bearer) │ │ (Rewind) ││
18
+ │ └──────────┘ └─────┬──────┘ └──────────┘ └───────────────────┘│
19
+ └─────────────────────┼───────────────────────────────────────────┘
20
+ │ JSON via FIFO (queue/controller.pipe)
21
+ ┌─────────────────────▼───────────────────────────────────────────┐
22
+ │ Controller Daemon (service/controller.sh) │
23
+ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌───────────────────┐ │
24
+ │ │ executor │ │ jobs.sh │ │ session │ │ worktree.sh │ │
25
+ │ │ (claude) │ │ (state) │ │ (conv.) │ │ (Git isolation) │ │
26
+ │ └──────────┘ └──────────┘ └───────────┘ └───────────────────┘ │
27
+ │ ┌───────────────────────────────────────────────────────────┐ │
28
+ │ │ checkpoint.sh (watch changes -> auto-commit -> rewind) │ │
29
+ │ └───────────────────────────────────────────────────────────┘ │
30
+ └──────────────────────┬──────────────────────────────────────────┘
31
+ │ claude -p --output-format stream-json
32
+
33
+ Claude Code CLI (headless)
34
+ ```
35
+
36
+ ## Project Structure
37
+
38
+ ```
39
+ controller/
40
+ ├── bin/ # Entry points
41
+ │ ├── controller # Service control (start/stop/restart/status)
42
+ │ ├── send # CLI client — sends tasks to FIFO
43
+ │ ├── start # Launch service + TUI together
44
+ │ ├── claude-sh # Interactive shell mode entry point
45
+ │ ├── native-app.py # Web server with auto browser launch
46
+ │ └── app-launcher.sh # macOS app launcher
47
+ ├── lib/ # Core modules (Bash)
48
+ │ ├── executor.sh # claude -p execution engine
49
+ │ ├── jobs.sh # Job registration / state / result management
50
+ │ ├── session.sh # Claude session ID tracking
51
+ │ ├── worktree.sh # Git Worktree create / remove / list
52
+ │ └── checkpoint.sh # Auto-checkpoint + Rewind
53
+ ├── service/
54
+ │ └── controller.sh # FIFO listener → dispatch persistent daemon
55
+ ├── web/ # HTTP REST API server (Python)
56
+ │ ├── server.py # Module entry point
57
+ │ ├── handler.py # REST API handler (GET/POST/DELETE)
58
+ │ ├── config.py # Paths / security / SSL settings
59
+ │ ├── auth.py # Token-based authentication
60
+ │ ├── jobs.py # Job CRUD + FIFO messaging
61
+ │ ├── checkpoint.py # Checkpoint queries + Rewind execution
62
+ │ ├── utils.py # Meta file parser, service status check
63
+ │ └── static/ # Web dashboard (Vanilla JS/CSS)
64
+ ├── config.sh # Global config (paths, model, permissions, worktree)
65
+ ├── data/ # Runtime data (settings.json, auth_token)
66
+ ├── logs/ # Job output (.out) + metadata (.meta)
67
+ ├── queue/ # FIFO pipe (controller.pipe)
68
+ ├── sessions/ # Session history (history.log)
69
+ ├── uploads/ # File upload storage
70
+ └── worktrees/ # Git Worktree storage
71
+ ```
72
+
73
+ ## Getting Started
74
+
75
+ ### Prerequisites
76
+
77
+ - macOS / Linux
78
+ - Claude Code CLI (`claude` command or app-bundled binary)
79
+ - Python 3.8+
80
+ - `jq` (JSON processing)
81
+ - Git (required for Worktree features)
82
+
83
+ ### Running
84
+
85
+ ```bash
86
+ # Start the service only
87
+ bin/controller start
88
+
89
+ # Start service + TUI together
90
+ bin/start
91
+
92
+ # Start the web server (auto-opens browser)
93
+ python3 bin/native-app.py
94
+
95
+ # Check service status
96
+ bin/controller status
97
+
98
+ # Stop the service
99
+ bin/controller stop
100
+ ```
101
+
102
+ ### Sending Tasks via CLI
103
+
104
+ ```bash
105
+ # Send a basic prompt
106
+ bin/send "Fix the bug in auth.py"
107
+
108
+ # Specify a working directory
109
+ bin/send --cwd /path/to/repo "Write test code"
110
+
111
+ # Run in an isolated Git Worktree
112
+ bin/send --worktree --repo /path/to/repo "Perform refactoring"
113
+
114
+ # Specify a custom task ID
115
+ bin/send --id my-task-1 "Write README"
116
+
117
+ # Check all task statuses
118
+ bin/send --status
119
+
120
+ # View task result
121
+ bin/send --result <task_id>
122
+ ```
123
+
124
+ ## Key Features
125
+
126
+ ### FIFO-Based Async Dispatch
127
+
128
+ The service daemon listens on a Named Pipe (`queue/controller.pipe`) for JSON messages and runs `claude -p` in the background. Supports duplicate prompt detection (3-second window), max concurrent job limits, and session modes (new/resume/fork/continue).
129
+
130
+ ```json
131
+ {
132
+ "id": "task-1",
133
+ "prompt": "Fix the bug",
134
+ "cwd": "/path/to/project",
135
+ "worktree": "true",
136
+ "session": "resume:<session_id>",
137
+ "images": ["/path/to/screenshot.png"]
138
+ }
139
+ ```
140
+
141
+ ### Git Worktree Isolation
142
+
143
+ Each task runs in an independent Git Worktree, enabling parallel work without affecting the main branch. A `controller/job-<id>` branch is automatically created and can be cleaned up after completion.
144
+
145
+ ### Auto-Checkpoint & Rewind
146
+
147
+ Periodically monitors file changes in the Worktree and auto-commits when changes stabilize. If something goes wrong, you can `git reset --hard` to a specific checkpoint and resume work with a new prompt that includes the previous conversation context (Rewind).
148
+
149
+ ### Session Management
150
+
151
+ Tracks Claude Code session IDs to enable conversation continuity:
152
+
153
+ - **new** — Start a fresh session
154
+ - **resume** — Continue an existing session (`--resume <session_id>`)
155
+ - **fork** — Branch from a previous session by injecting its context
156
+ - **continue** — Continue the most recent conversation (`--continue`)
157
+
158
+ ## REST API
159
+
160
+ | Method | Endpoint | Description |
161
+ |--------|----------|-------------|
162
+ | GET | `/api/status` | Service running status |
163
+ | GET | `/api/jobs` | List all jobs |
164
+ | GET | `/api/jobs/:id/result` | Get job result |
165
+ | GET | `/api/jobs/:id/stream` | Poll real-time stream (offset-based) |
166
+ | GET | `/api/jobs/:id/checkpoints` | List checkpoints |
167
+ | GET | `/api/sessions` | List sessions (Claude Code native + job meta) |
168
+ | GET | `/api/session/:id/job` | Find job by session ID |
169
+ | GET | `/api/config` | Get configuration |
170
+ | GET | `/api/recent-dirs` | Get recent working directories |
171
+ | GET | `/api/dirs?path=` | Browse filesystem directories |
172
+ | POST | `/api/send` | Send a new task (via FIFO) |
173
+ | POST | `/api/upload` | Upload a file (base64) |
174
+ | POST | `/api/jobs/:id/rewind` | Rewind to a checkpoint |
175
+ | POST | `/api/service/start` | Start the service |
176
+ | POST | `/api/service/stop` | Stop the service |
177
+ | POST | `/api/config` | Save configuration |
178
+ | POST | `/api/auth/verify` | Verify auth token |
179
+ | DELETE | `/api/jobs/:id` | Delete a job |
180
+ | DELETE | `/api/jobs` | Bulk delete completed jobs |
181
+
182
+ ## Security
183
+
184
+ Three-layer security model:
185
+
186
+ 1. **Host Header Validation** — Only allows `localhost`, `127.0.0.1`, `[::1]` to prevent DNS Rebinding attacks.
187
+ 2. **Origin Validation (CORS)** — Only accepts cross-origin requests from an allowed Origin list.
188
+ 3. **Token Authentication** — Generates a random token on server startup. When `AUTH_REQUIRED=true`, all API requests must include an `Authorization: Bearer <token>` header.
189
+
190
+ ### SSL/HTTPS
191
+
192
+ Generate local certificates with `mkcert` to enable HTTPS mode:
193
+
194
+ ```bash
195
+ mkcert -install
196
+ mkcert -cert-file certs/localhost+1.pem -key-file certs/localhost+1-key.pem localhost 127.0.0.1
197
+ ```
198
+
199
+ ## Configuration
200
+
201
+ Override settings via `data/settings.json` or environment variables:
202
+
203
+ | Setting | Default | Description |
204
+ |---------|---------|-------------|
205
+ | `skip_permissions` | `true` | Use `--dangerously-skip-permissions` flag |
206
+ | `model` | `""` | Claude model override (empty = default model) |
207
+ | `max_jobs` | `10` | Max concurrent background jobs |
208
+ | `target_repo` | `""` | Git repository path for Worktree creation |
209
+ | `base_branch` | `main` | Base branch for Worktree |
210
+ | `checkpoint_interval` | `5` | Checkpoint watch interval (seconds) |
211
+ | `append_system_prompt` | `""` | Additional system prompt text |
212
+ | `allowed_tools` | All tools | Tool allowlist for Claude |
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/cmux.app/Contents/Resources/bin:$PATH"
3
+ _src="$0"
4
+ while [[ -L "$_src" ]]; do
5
+ _dir="$(cd "$(dirname "$_src")" && pwd)"
6
+ _src="$(readlink "$_src")"
7
+ [[ "$_src" != /* ]] && _src="$_dir/$_src"
8
+ done
9
+ CONTROLLER_DIR="$(cd "$(dirname "$_src")/.." && pwd)"
10
+ cd "$CONTROLLER_DIR" || exit 1
11
+ source config.sh
12
+ mkdir -p "$LOGS_DIR" "$QUEUE_DIR" "$SESSIONS_DIR" "$WORKTREES_DIR"
13
+
14
+ # 서비스 시작
15
+ if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then true; else
16
+ rm -f "$FIFO_PATH" "$PID_FILE" 2>/dev/null
17
+ bash service/controller.sh start >> "$LOGS_DIR/service.log" 2>&1 &
18
+ sleep 2
19
+ fi
20
+
21
+ # 네이티브 앱 실행
22
+ exec /opt/homebrew/bin/python3 bin/native-app.py 2>> "$LOGS_DIR/app.log"
package/bin/claude-sh ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # claude-sh — Claude Shell Controller 진입점
4
+ #
5
+ # 사용법:
6
+ # claude-sh 인터랙티브 쉘 모드
7
+ # claude-sh "프롬프트" 단일 명령 실행
8
+ # claude-sh "프롬프트" & 백그라운드 실행
9
+ # echo "프롬프트" | claude-sh 파이프라인 입력
10
+ # ============================================================
11
+
12
+ _src="${BASH_SOURCE[0]}"
13
+ while [[ -L "$_src" ]]; do
14
+ _dir="$(cd "$(dirname "$_src")" && pwd)"
15
+ _src="$(readlink "$_src")"
16
+ [[ "$_src" != /* ]] && _src="$_dir/$_src"
17
+ done
18
+ SCRIPT_DIR="$(cd "$(dirname "$_src")/.." && pwd)"
19
+ exec bash "${SCRIPT_DIR}/claude-shell.sh" "$@"
package/bin/controller ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+ # Controller service entry point
3
+ # Usage:
4
+ # controller start — 서비스 시작
5
+ # controller stop — 서비스 중지
6
+ # controller restart — 재시작
7
+ # controller status — 상태 확인
8
+
9
+ _src="${BASH_SOURCE[0]}"
10
+ while [[ -L "$_src" ]]; do
11
+ _dir="$(cd "$(dirname "$_src")" && pwd)"
12
+ _src="$(readlink "$_src")"
13
+ [[ "$_src" != /* ]] && _src="$_dir/$_src"
14
+ done
15
+ SCRIPT_DIR="$(cd "$(dirname "$_src")/.." && pwd)"
16
+ source "${SCRIPT_DIR}/config.sh"
17
+
18
+ case "${1:-start}" in
19
+ start)
20
+ exec bash "${SCRIPT_DIR}/service/controller.sh" start
21
+ ;;
22
+ stop)
23
+ exec bash "${SCRIPT_DIR}/service/controller.sh" stop
24
+ ;;
25
+ restart)
26
+ bash "${SCRIPT_DIR}/service/controller.sh" stop
27
+ sleep 1
28
+ exec bash "${SCRIPT_DIR}/service/controller.sh" start
29
+ ;;
30
+ status)
31
+ exec bash "${SCRIPT_DIR}/service/controller.sh" status
32
+ ;;
33
+ *)
34
+ echo "사용법: controller {start|stop|restart|status}"
35
+ exit 1
36
+ ;;
37
+ esac
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Claude Controller — 웹 서버 실행 + 브라우저 자동 오픈
4
+ SSL/HTTPS 지원 + 토큰 인증
5
+ """
6
+ import http.server
7
+ import os
8
+ import ssl
9
+ import subprocess
10
+ import sys
11
+ import time
12
+ import webbrowser
13
+ from pathlib import Path
14
+
15
+ CONTROLLER_DIR = Path(__file__).resolve().parent.parent
16
+ SERVICE_SCRIPT = CONTROLLER_DIR / "service" / "controller.sh"
17
+ PID_FILE = CONTROLLER_DIR / "service" / "controller.pid"
18
+ LOGS_DIR = CONTROLLER_DIR / "logs"
19
+ PORT = int(os.environ.get("PORT", 8420))
20
+
21
+
22
+ def is_service_running():
23
+ if not PID_FILE.exists():
24
+ return False, None
25
+ try:
26
+ pid = int(PID_FILE.read_text().strip())
27
+ os.kill(pid, 0)
28
+ return True, pid
29
+ except (ValueError, ProcessLookupError, PermissionError, OSError):
30
+ return False, None
31
+
32
+
33
+ def main():
34
+ for d in ["logs", "queue", "sessions", "uploads", "data", "certs"]:
35
+ (CONTROLLER_DIR / d).mkdir(parents=True, exist_ok=True)
36
+
37
+ # 서비스 시작
38
+ ok, pid = is_service_running()
39
+ if not ok:
40
+ log_fh = open(LOGS_DIR / "service.log", "a")
41
+ subprocess.Popen(["bash", str(SERVICE_SCRIPT), "start"],
42
+ stdout=log_fh, stderr=subprocess.STDOUT,
43
+ stdin=subprocess.DEVNULL, start_new_session=True, cwd=str(CONTROLLER_DIR))
44
+ log_fh.close()
45
+ for _ in range(30):
46
+ time.sleep(0.1)
47
+ ok, pid = is_service_running()
48
+ if ok:
49
+ break
50
+
51
+ # 웹 모듈 임포트
52
+ sys.path.insert(0, str(CONTROLLER_DIR / "web"))
53
+ from server import ControllerHandler
54
+ from config import SSL_CERT, SSL_KEY, PUBLIC_URL
55
+ from auth import generate_token
56
+
57
+ # 토큰 생성 (매 시작 시 새로 발급)
58
+ token = generate_token()
59
+
60
+ # SSL 인증서 확인
61
+ use_ssl = os.path.isfile(SSL_CERT) and os.path.isfile(SSL_KEY)
62
+ scheme = "https" if use_ssl else "http"
63
+
64
+ server = http.server.HTTPServer(("127.0.0.1", PORT), ControllerHandler)
65
+
66
+ if use_ssl:
67
+ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
68
+ ctx.load_cert_chain(SSL_CERT, SSL_KEY)
69
+ server.socket = ctx.wrap_socket(server.socket, server_side=True)
70
+
71
+ # 시작 배너
72
+ print(f"""
73
+ ┌──────────────────────────────────────────────┐
74
+ │ Claude Controller │
75
+ ├──────────────────────────────────────────────┤
76
+ │ API : {scheme}://localhost:{PORT:<24s}│
77
+ │ App : {PUBLIC_URL:<35s}│
78
+ │ SSL : {'ON' if use_ssl else 'OFF (HTTP 모드)':<35s}│
79
+ ├──────────────────────────────────────────────┤
80
+ │ Auth Token (아래 토큰을 프론트엔드에 입력): │
81
+ │ {token:<43s}│
82
+ ├──────────────────────────────────────────────┤
83
+ │ 종료 : Ctrl+C │
84
+ └──────────────────────────────────────────────┘
85
+ """)
86
+
87
+ if not use_ssl:
88
+ print(f" [참고] HTTPS를 사용하려면 mkcert로 인증서를 생성하세요:")
89
+ print(f" mkcert -install && mkcert -cert-file certs/localhost+1.pem \\")
90
+ print(f" -key-file certs/localhost+1-key.pem localhost 127.0.0.1\n")
91
+
92
+ webbrowser.open(PUBLIC_URL)
93
+
94
+ try:
95
+ server.serve_forever()
96
+ except KeyboardInterrupt:
97
+ print("\n 종료됨.")
98
+ server.server_close()
99
+
100
+
101
+ if __name__ == "__main__":
102
+ main()
package/bin/send ADDED
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # send — Controller 서비스에 명령을 전송하는 클라이언트
4
+ # ============================================================
5
+ # Usage:
6
+ # send "Fix the bug in auth.py"
7
+ # send --cwd /path/to/repo "Fix the bug"
8
+ # send --id my-task-1 "Fix the bug"
9
+ # send --status
10
+ # send --result <job_id>
11
+ # send --stop
12
+ # ============================================================
13
+
14
+ set -euo pipefail
15
+
16
+ _src="${BASH_SOURCE[0]}"
17
+ while [[ -L "$_src" ]]; do
18
+ _dir="$(cd "$(dirname "$_src")" && pwd)"
19
+ _src="$(readlink "$_src")"
20
+ [[ "$_src" != /* ]] && _src="$_dir/$_src"
21
+ done
22
+ SCRIPT_DIR="$(cd "$(dirname "$_src")" && pwd)"
23
+ source "${SCRIPT_DIR}/../config.sh"
24
+ source "${SCRIPT_DIR}/../lib/jobs.sh"
25
+
26
+ # ── 사용법 출력 ─────────────────────────────────────────────
27
+ usage() {
28
+ cat <<'USAGE'
29
+ 사용법:
30
+ send "프롬프트" 프롬프트 전송
31
+ send --cwd <경로> "프롬프트" 작업 디렉토리 지정하여 전송
32
+ send --id <작업ID> "프롬프트" 사용자 지정 ID로 전송
33
+ send --worktree [--repo <경로>] "프롬프트" worktree에서 격리 실행
34
+ send --status 전체 작업 상태 조회
35
+ send --result <작업ID> 작업 결과 조회
36
+ send --stop 서비스 종료
37
+ send --help 이 도움말 출력
38
+ USAGE
39
+ }
40
+
41
+ # ── UUID 생성 ───────────────────────────────────────────────
42
+ generate_id() {
43
+ echo "$(date +%s)-$$-${RANDOM}"
44
+ }
45
+
46
+ # ── 서비스 실행 여부 확인 ───────────────────────────────────
47
+ check_service() {
48
+ if [[ ! -p "$FIFO_PATH" ]]; then
49
+ echo "[오류] 서비스가 실행 중이 아닙니다. (FIFO 없음: $FIFO_PATH)" >&2
50
+ echo " 먼저 서비스를 시작하세요." >&2
51
+ exit 1
52
+ fi
53
+ }
54
+
55
+ # ── 플래그 파싱 ─────────────────────────────────────────────
56
+ CWD=""
57
+ JOB_ID=""
58
+ ACTION="send" # send | status | result | stop
59
+ USE_WORKTREE=""
60
+ REPO_PATH=""
61
+
62
+ while [[ $# -gt 0 ]]; do
63
+ case "$1" in
64
+ --cwd)
65
+ CWD="$2"
66
+ shift 2
67
+ ;;
68
+ --id)
69
+ JOB_ID="$2"
70
+ shift 2
71
+ ;;
72
+ --worktree)
73
+ USE_WORKTREE="true"
74
+ shift
75
+ ;;
76
+ --repo)
77
+ REPO_PATH="$2"
78
+ shift 2
79
+ ;;
80
+ --status)
81
+ ACTION="status"
82
+ shift
83
+ ;;
84
+ --result)
85
+ ACTION="result"
86
+ RESULT_JOB_ID="$2"
87
+ shift 2
88
+ ;;
89
+ --stop)
90
+ ACTION="stop"
91
+ shift
92
+ ;;
93
+ --help|-h)
94
+ usage
95
+ exit 0
96
+ ;;
97
+ -*)
98
+ echo "[오류] 알 수 없는 옵션: $1" >&2
99
+ usage
100
+ exit 1
101
+ ;;
102
+ *)
103
+ PROMPT="$1"
104
+ shift
105
+ ;;
106
+ esac
107
+ done
108
+
109
+ # ── 액션 실행 ───────────────────────────────────────────────
110
+ case "$ACTION" in
111
+
112
+ status)
113
+ jobs_list
114
+ ;;
115
+
116
+ result)
117
+ if [[ -z "${RESULT_JOB_ID:-}" ]]; then
118
+ echo "[오류] --result 에 작업 ID를 지정하세요." >&2
119
+ exit 1
120
+ fi
121
+ job_result "$RESULT_JOB_ID"
122
+ ;;
123
+
124
+ stop)
125
+ if [[ ! -f "$PID_FILE" ]]; then
126
+ echo "[오류] PID 파일을 찾을 수 없습니다. ($PID_FILE)" >&2
127
+ echo " 서비스가 실행 중이 아닌 것 같습니다." >&2
128
+ exit 1
129
+ fi
130
+ SERVICE_PID=$(cat "$PID_FILE")
131
+ if kill -0 "$SERVICE_PID" 2>/dev/null; then
132
+ kill -TERM "$SERVICE_PID"
133
+ echo "[완료] 서비스(PID: $SERVICE_PID)에 종료 신호를 전송했습니다."
134
+ else
135
+ echo "[알림] 서비스(PID: $SERVICE_PID)는 이미 종료된 상태입니다."
136
+ rm -f "$PID_FILE"
137
+ fi
138
+ ;;
139
+
140
+ send)
141
+ if [[ -z "${PROMPT:-}" ]]; then
142
+ echo "[오류] 전송할 프롬프트를 입력하세요." >&2
143
+ usage
144
+ exit 1
145
+ fi
146
+
147
+ check_service
148
+
149
+ # ID가 없으면 자동 생성
150
+ [[ -z "$JOB_ID" ]] && JOB_ID=$(generate_id)
151
+
152
+ # CWD 기본값: 현재 디렉토리
153
+ [[ -z "$CWD" ]] && CWD="$(pwd)"
154
+
155
+ # JSON 페이로드 구성 (jq 사용 — 특수문자 안전 처리)
156
+ JQ_ARGS=(
157
+ --arg id "$JOB_ID"
158
+ --arg prompt "$PROMPT"
159
+ --arg cwd "$CWD"
160
+ )
161
+ JQ_EXPR='{id: $id, prompt: $prompt, cwd: $cwd}'
162
+
163
+ if [[ -n "$USE_WORKTREE" ]]; then
164
+ JQ_ARGS+=(--arg worktree "true")
165
+ JQ_EXPR='{id: $id, prompt: $prompt, cwd: $cwd, worktree: $worktree}'
166
+ if [[ -n "$REPO_PATH" ]]; then
167
+ JQ_ARGS+=(--arg repo "$REPO_PATH")
168
+ JQ_EXPR='{id: $id, prompt: $prompt, cwd: $cwd, worktree: $worktree, repo: $repo}'
169
+ fi
170
+ fi
171
+
172
+ PAYLOAD=$(jq -cn "${JQ_ARGS[@]}" "$JQ_EXPR")
173
+
174
+ # FIFO에 전송
175
+ echo "$PAYLOAD" > "$FIFO_PATH"
176
+
177
+ echo "[전송 완료] 작업이 서비스로 전달되었습니다."
178
+ echo " 작업 ID : $JOB_ID"
179
+ echo " 디렉토리: $CWD"
180
+ [[ -n "$USE_WORKTREE" ]] && echo " 워크트리: ON${REPO_PATH:+ (repo: $REPO_PATH)}"
181
+ display_prompt="${PROMPT:0:80}"
182
+ [[ ${#PROMPT} -gt 80 ]] && display_prompt="${display_prompt}..."
183
+ echo " 프롬프트: $display_prompt"
184
+ ;;
185
+ esac
package/bin/start ADDED
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # start — Controller 서비스 + TUI 일괄 실행
4
+ # Usage: ./start [--repo /path/to/git/repo]
5
+ # ============================================================
6
+ set -uo pipefail
7
+
8
+ _src="${BASH_SOURCE[0]}"
9
+ while [[ -L "$_src" ]]; do
10
+ _dir="$(cd "$(dirname "$_src")" && pwd)"
11
+ _src="$(readlink "$_src")"
12
+ [[ "$_src" != /* ]] && _src="$_dir/$_src"
13
+ done
14
+ SCRIPT_DIR="$(cd "$(dirname "$_src")/.." && pwd)"
15
+ source "${SCRIPT_DIR}/config.sh"
16
+
17
+ # 옵션 파싱
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --repo)
21
+ export TARGET_REPO="$2"
22
+ shift 2
23
+ ;;
24
+ *)
25
+ shift
26
+ ;;
27
+ esac
28
+ done
29
+
30
+ # 사전 검증
31
+ if [[ -z "$CLAUDE_BIN" ]]; then
32
+ echo "[오류] claude CLI를 찾을 수 없습니다."; exit 1
33
+ fi
34
+ if ! command -v python3 &>/dev/null; then
35
+ echo "[오류] python3가 필요합니다."; exit 1
36
+ fi
37
+
38
+ mkdir -p "$LOGS_DIR" "$QUEUE_DIR" "$SESSIONS_DIR" "$WORKTREES_DIR"
39
+
40
+ echo ""
41
+ echo " ╔══════════════════════════════════════════╗"
42
+ echo " ║ Controller Service + TUI ║"
43
+ echo " ╚══════════════════════════════════════════╝"
44
+ echo ""
45
+
46
+ # 컨트롤러 서비스 시작
47
+ if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
48
+ echo " [서비스] 이미 실행 중 (PID: $(cat "$PID_FILE"))"
49
+ else
50
+ echo " [서비스] 컨트롤러 데몬 시작 중..."
51
+ bash "${SCRIPT_DIR}/service/controller.sh" start &
52
+ sleep 2
53
+ if [[ -f "$PID_FILE" ]]; then
54
+ echo " [서비스] 시작됨 (PID: $(cat "$PID_FILE"))"
55
+ fi
56
+ fi
57
+
58
+ echo ""
59
+ echo " [TUI] 터미널 프로그램을 시작합니다..."
60
+ echo " [TUI] 종료: q 키"
61
+ echo ""
62
+
63
+ # Ctrl+C로 서비스도 함께 종료
64
+ cleanup_all() {
65
+ if [[ -f "$PID_FILE" ]]; then
66
+ kill "$(cat "$PID_FILE")" 2>/dev/null
67
+ fi
68
+ echo ""
69
+ echo " 종료됨."
70
+ }
71
+ trap cleanup_all INT TERM
72
+
73
+ # TUI 포그라운드 실행
74
+ sleep 1
75
+ exec python3 "${SCRIPT_DIR}/bin/tui"