@wenaixi/dsh-superpower 7.2.1 → 7.4.1

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.
@@ -1,204 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Start the brainstorm server and output connection info
3
- # Usage: start-server.sh [--project-dir <path>] [--host <bind-host>] [--url-host <display-host>] [--foreground] [--background]
4
- #
5
- # Starts server on a random high port, outputs JSON with URL.
6
- # Each session gets its own directory to avoid conflicts.
7
- #
8
- # Options:
9
- # --project-dir <path> Store session files under <path>/.superpowers/brainstorm/
10
- # instead of /tmp. Files persist after server stops.
11
- # --host <bind-host> Host/interface to bind (default: 127.0.0.1).
12
- # Use 0.0.0.0 in remote/containerized environments.
13
- # --url-host <host> Hostname shown in returned URL JSON.
14
- # --idle-timeout-minutes <n> Shut down after n minutes idle (default 240 = 4h).
15
- # --open Auto-open the browser on the first screen (use only
16
- # after the user approves the visual companion).
17
- # --foreground Run server in the current terminal (no backgrounding).
18
- # --background Force background mode.
19
-
20
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
-
22
- # Parse arguments
23
- PROJECT_DIR=""
24
- FOREGROUND="false"
25
- FORCE_BACKGROUND="false"
26
- BIND_HOST="127.0.0.1"
27
- URL_HOST=""
28
- IDLE_TIMEOUT_MINUTES=""
29
- while [[ $# -gt 0 ]]; do
30
- case "$1" in
31
- --project-dir)
32
- PROJECT_DIR="$2"
33
- shift 2
34
- ;;
35
- --host)
36
- BIND_HOST="$2"
37
- shift 2
38
- ;;
39
- --url-host)
40
- URL_HOST="$2"
41
- shift 2
42
- ;;
43
- --idle-timeout-minutes)
44
- IDLE_TIMEOUT_MINUTES="$2"
45
- shift 2
46
- ;;
47
- --open)
48
- export BRAINSTORM_OPEN=1
49
- shift
50
- ;;
51
- --foreground|--no-daemon)
52
- FOREGROUND="true"
53
- shift
54
- ;;
55
- --background|--daemon)
56
- FORCE_BACKGROUND="true"
57
- shift
58
- ;;
59
- *)
60
- echo "{\"error\": \"Unknown argument: $1\"}"
61
- exit 1
62
- ;;
63
- esac
64
- done
65
-
66
- if [[ -z "$URL_HOST" ]]; then
67
- if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then
68
- URL_HOST="localhost"
69
- else
70
- URL_HOST="$BIND_HOST"
71
- fi
72
- fi
73
-
74
- if [[ -n "$IDLE_TIMEOUT_MINUTES" ]]; then
75
- if ! [[ "$IDLE_TIMEOUT_MINUTES" =~ ^[0-9]+$ ]] || [[ "$IDLE_TIMEOUT_MINUTES" -lt 1 ]]; then
76
- echo "{\"error\": \"--idle-timeout-minutes must be a positive integer\"}"
77
- exit 1
78
- fi
79
- export BRAINSTORM_IDLE_TIMEOUT_MS=$(( IDLE_TIMEOUT_MINUTES * 60 * 1000 ))
80
- fi
81
-
82
- is_windows_like_shell() {
83
- case "${OSTYPE:-}" in
84
- msys*|cygwin*|mingw*) return 0 ;;
85
- esac
86
- if [[ -n "${MSYSTEM:-}" ]]; then
87
- return 0
88
- fi
89
- local uname_s
90
- uname_s="$(uname -s 2>/dev/null || true)"
91
- case "$uname_s" in
92
- MSYS*|MINGW*|CYGWIN*) return 0 ;;
93
- esac
94
- return 1
95
- }
96
-
97
- # Windows/Git Bash reaps nohup background processes. Auto-foreground when detected.
98
- if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then
99
- if is_windows_like_shell; then
100
- FOREGROUND="true"
101
- fi
102
- fi
103
-
104
- # Session files (server.log, server-info, .last-token) embed the session key —
105
- # keep everything this script and the server create owner-only.
106
- umask 077
107
-
108
- # Generate unique session directory
109
- SESSION_ID="$$-$(date +%s)"
110
-
111
- if [[ -n "$PROJECT_DIR" ]]; then
112
- SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}"
113
- # Persist the bound port and key per project so a restart reuses them and an
114
- # already-open browser tab reconnects to the same URL with a valid cookie.
115
- export BRAINSTORM_PORT_FILE="${PROJECT_DIR}/.superpowers/brainstorm/.last-port"
116
- export BRAINSTORM_TOKEN_FILE="${PROJECT_DIR}/.superpowers/brainstorm/.last-token"
117
- else
118
- SESSION_DIR="/tmp/brainstorm-${SESSION_ID}"
119
- fi
120
-
121
- STATE_DIR="${SESSION_DIR}/state"
122
- PID_FILE="${STATE_DIR}/server.pid"
123
- LOG_FILE="${STATE_DIR}/server.log"
124
- SERVER_ID_FILE="${STATE_DIR}/server-instance-id"
125
-
126
- # Create fresh session directory with content and state peers
127
- mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"
128
-
129
- SERVER_ID=""
130
- if [[ -r /dev/urandom ]]; then
131
- SERVER_ID="$(od -An -N24 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' || true)"
132
- fi
133
- if ! [[ "$SERVER_ID" =~ ^[A-Za-z0-9_-]{32,64}$ ]]; then
134
- SERVER_ID="$(printf '%08x%08x%08x%08x' "$$" "$(date +%s)" "${RANDOM:-0}" "${RANDOM:-0}")"
135
- fi
136
- printf '%s\n' "$SERVER_ID" > "$SERVER_ID_FILE"
137
- chmod 600 "$SERVER_ID_FILE" 2>/dev/null || true
138
-
139
- # Kill any existing server
140
- if [[ -f "$PID_FILE" ]]; then
141
- old_pid=$(cat "$PID_FILE")
142
- kill "$old_pid" 2>/dev/null
143
- rm -f "$PID_FILE"
144
- fi
145
-
146
- cd "$SCRIPT_DIR" || exit 1
147
-
148
- # Resolve the harness PID (grandparent of this script).
149
- # $PPID is the ephemeral shell the harness spawned to run us — it dies
150
- # when this script exits. The harness itself is $PPID's parent.
151
- OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')"
152
- if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then
153
- OWNER_PID="$PPID"
154
- fi
155
-
156
- # Windows/MSYS2: Node.js cannot see POSIX PIDs from the MSYS2 namespace.
157
- # Passing a PID node cannot verify causes server to log owner-pid-invalid
158
- # and self-terminate at the 60-second lifecycle check. Clear it so the
159
- # watchdog is disabled and the idle timeout becomes the only shutdown trigger.
160
- if is_windows_like_shell; then
161
- OWNER_PID=""
162
- fi
163
-
164
- # Foreground mode for environments that reap detached/background processes.
165
- if [[ "$FOREGROUND" == "true" ]]; then
166
- env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" &
167
- SERVER_PID=$!
168
- echo "$SERVER_PID" > "$PID_FILE"
169
- wait "$SERVER_PID"
170
- exit $?
171
- fi
172
-
173
- # Start server, capturing output to log file
174
- # Use nohup to survive shell exit; disown to remove from job table
175
- nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" > "$LOG_FILE" 2>&1 &
176
- SERVER_PID=$!
177
- disown "$SERVER_PID" 2>/dev/null
178
- echo "$SERVER_PID" > "$PID_FILE"
179
-
180
- # Wait for server-started message (check log file)
181
- for _ in {1..50}; do
182
- if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then
183
- # Verify server is still alive after a short window (catches process reapers)
184
- alive="true"
185
- for _ in {1..20}; do
186
- if ! kill -0 "$SERVER_PID" 2>/dev/null; then
187
- alive="false"
188
- break
189
- fi
190
- sleep 0.1
191
- done
192
- if [[ "$alive" != "true" ]]; then
193
- echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}"
194
- exit 1
195
- fi
196
- grep "server-started" "$LOG_FILE" | head -1
197
- exit 0
198
- fi
199
- sleep 0.1
200
- done
201
-
202
- # Timeout - server didn't start
203
- echo '{"error": "Server failed to start within 5 seconds"}'
204
- exit 1
@@ -1,120 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Stop the brainstorm server and clean up
3
- # Usage: stop-server.sh <session_dir>
4
- #
5
- # Kills the server process. Only deletes session directory if it's
6
- # under /tmp (ephemeral). Persistent directories (.superpowers/) are
7
- # kept so mockups can be reviewed later.
8
-
9
- SESSION_DIR="$1"
10
-
11
- if [[ -z "$SESSION_DIR" ]]; then
12
- echo '{"error": "Usage: stop-server.sh <session_dir>"}'
13
- exit 1
14
- fi
15
-
16
- STATE_DIR="${SESSION_DIR}/state"
17
- PID_FILE="${STATE_DIR}/server.pid"
18
- SERVER_ID_FILE="${STATE_DIR}/server-instance-id"
19
-
20
- mark_stopped() {
21
- local reason="$1"
22
- rm -f "${STATE_DIR}/server-info"
23
- printf '{"reason":"%s","timestamp":%s}\n' "$reason" "$(date +%s)" > "${STATE_DIR}/server-stopped"
24
- }
25
-
26
- read_expected_server_id() {
27
- [[ -f "$SERVER_ID_FILE" ]] || return 1
28
- local id
29
- id="$(tr -d '\r\n' < "$SERVER_ID_FILE" 2>/dev/null || true)"
30
- [[ "$id" =~ ^[A-Za-z0-9_-]{32,64}$ ]] || return 1
31
- printf '%s\n' "$id"
32
- }
33
-
34
- command_line_for_pid() {
35
- local pid="$1"
36
- if [[ -r "/proc/$pid/cmdline" ]]; then
37
- tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null || true
38
- return 0
39
- fi
40
- ps -ww -p "$pid" -o command= 2>/dev/null || ps -f -p "$pid" 2>/dev/null | sed '1d' || true
41
- }
42
-
43
- command_has_server_id() {
44
- local pid="$1"
45
- local expected="$2"
46
- local expected_arg="--brainstorm-server-id=$expected"
47
- if [[ -r "/proc/$pid/cmdline" ]]; then
48
- local arg
49
- while IFS= read -r -d '' arg || [[ -n "$arg" ]]; do
50
- [[ "$arg" == "$expected_arg" ]] && return 0
51
- done < "/proc/$pid/cmdline"
52
- return 1
53
- fi
54
- local command_line
55
- command_line="$(command_line_for_pid "$pid")"
56
- [[ -n "$command_line" ]] || return 1
57
- case " $command_line " in
58
- *" $expected_arg "*) return 0 ;;
59
- *) return 1 ;;
60
- esac
61
- }
62
-
63
- # Confirm a PID has this session's per-start instance id, not just a familiar
64
- # process name. Ambiguous or legacy metadata fails closed as stale_pid.
65
- is_brainstorm_server() {
66
- kill -0 "$1" 2>/dev/null || return 1
67
- local expected_id
68
- expected_id="$(read_expected_server_id)" || return 1
69
- command_has_server_id "$1" "$expected_id" || return 1
70
- return 0
71
- }
72
-
73
- if [[ -f "$PID_FILE" ]]; then
74
- pid=$(cat "$PID_FILE")
75
-
76
- # Refuse to signal a PID we can't prove is our server. A stale pid file may
77
- # point at an unrelated process after a reboot/PID wraparound.
78
- if ! is_brainstorm_server "$pid"; then
79
- rm -f "$PID_FILE" "$SERVER_ID_FILE"
80
- mark_stopped "stale_pid"
81
- echo '{"status": "stale_pid"}'
82
- exit 0
83
- fi
84
-
85
- # Try to stop gracefully, fallback to force if still alive
86
- kill "$pid" 2>/dev/null || true
87
-
88
- # Wait for graceful shutdown (up to ~2s)
89
- for _ in {1..20}; do
90
- if ! kill -0 "$pid" 2>/dev/null; then
91
- break
92
- fi
93
- sleep 0.1
94
- done
95
-
96
- # If still running, escalate to SIGKILL
97
- if kill -0 "$pid" 2>/dev/null; then
98
- kill -9 "$pid" 2>/dev/null || true
99
-
100
- # Give SIGKILL a moment to take effect
101
- sleep 0.1
102
- fi
103
-
104
- if kill -0 "$pid" 2>/dev/null; then
105
- echo '{"status": "failed", "error": "process still running"}'
106
- exit 1
107
- fi
108
-
109
- rm -f "$PID_FILE" "$SERVER_ID_FILE" "${STATE_DIR}/server.log"
110
- mark_stopped "stop-server.sh"
111
-
112
- # Only delete ephemeral /tmp directories
113
- if [[ "$SESSION_DIR" == /tmp/* ]]; then
114
- rm -rf "$SESSION_DIR"
115
- fi
116
-
117
- echo '{"status": "stopped"}'
118
- else
119
- echo '{"status": "not_running"}'
120
- fi