crewly 1.4.15 → 1.4.17

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,279 +0,0 @@
1
- #!/usr/bin/env bash
2
- # =============================================================================
3
- # Chrome Live Attach — One-click attach to user's running Chrome browser
4
- #
5
- # Auto-discovers Chrome processes with CDP (Chrome DevTools Protocol) enabled,
6
- # or offers to enable CDP on the user's existing Chrome session.
7
- #
8
- # Modes:
9
- # discover — Scan for Chrome instances (default)
10
- # attach — Connect to a specific CDP port
11
- # launch — Launch Chrome with CDP on user's default profile
12
- #
13
- # Usage:
14
- # execute.sh '{"mode":"discover"}'
15
- # execute.sh '{"mode":"attach","port":9222}'
16
- # execute.sh '{"mode":"launch","port":9222}'
17
- #
18
- # @see https://github.com/stevehuang0115/crewly/issues/175
19
- # =============================================================================
20
-
21
- set -euo pipefail
22
-
23
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
- source "$SCRIPT_DIR/../../_common/lib.sh"
25
-
26
- # Parse input
27
- INPUT="${1:-{}}"
28
- MODE=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('mode','discover'))" 2>/dev/null || echo "discover")
29
- PORT=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('port',9222))" 2>/dev/null || echo "9222")
30
-
31
- # ── Helper functions ────────────────────────────────────────
32
-
33
- log() { echo "[chrome-attach] $*" >&2; }
34
-
35
- check_cdp() {
36
- local port="$1"
37
- curl -sf --max-time 2 "http://127.0.0.1:${port}/json/version" 2>/dev/null
38
- }
39
-
40
- get_ws_url() {
41
- local port="$1"
42
- curl -sf --max-time 2 "http://127.0.0.1:${port}/json/version" \
43
- | python3 -c "import sys,json; print(json.load(sys.stdin).get('webSocketDebuggerUrl',''))" 2>/dev/null || true
44
- }
45
-
46
- is_chrome_running() {
47
- pgrep -f "Google Chrome" >/dev/null 2>&1
48
- }
49
-
50
- find_chrome_binary() {
51
- local candidates=(
52
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
53
- "google-chrome"
54
- "google-chrome-stable"
55
- )
56
- for c in "${candidates[@]}"; do
57
- if command -v "$c" &>/dev/null || [ -x "$c" ]; then
58
- echo "$c"
59
- return 0
60
- fi
61
- done
62
- return 1
63
- }
64
-
65
- get_default_profile() {
66
- case "$(uname)" in
67
- Darwin) echo "$HOME/Library/Application Support/Google/Chrome" ;;
68
- Linux) echo "$HOME/.config/google-chrome" ;;
69
- *) echo "$HOME/.config/google-chrome" ;;
70
- esac
71
- }
72
-
73
- # ── Mode: Discover ─────────────────────────────────────────
74
-
75
- discover_chrome() {
76
- local found_instances="[]"
77
- local chrome_running="false"
78
-
79
- if is_chrome_running; then
80
- chrome_running="true"
81
- fi
82
-
83
- # Scan common CDP ports
84
- for p in 9222 9229 9223 9224; do
85
- local version_json
86
- version_json=$(check_cdp "$p" 2>/dev/null) || continue
87
-
88
- local ws_url browser_version
89
- ws_url=$(echo "$version_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('webSocketDebuggerUrl',''))" 2>/dev/null || echo "")
90
- browser_version=$(echo "$version_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('Browser','unknown'))" 2>/dev/null || echo "unknown")
91
-
92
- found_instances=$(echo "$found_instances" | python3 -c "
93
- import sys, json
94
- instances = json.load(sys.stdin)
95
- instances.append({
96
- 'port': $p,
97
- 'wsUrl': '$ws_url',
98
- 'httpEndpoint': 'http://127.0.0.1:$p',
99
- 'version': '$browser_version',
100
- 'isPrimary': $p == 9222
101
- })
102
- json.dump(instances, sys.stdout)
103
- " 2>/dev/null || echo "$found_instances")
104
- done
105
-
106
- local count
107
- count=$(echo "$found_instances" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
108
-
109
- local suggestion=""
110
- if [ "$count" = "0" ] && [ "$chrome_running" = "true" ]; then
111
- suggestion="Chrome is running but without CDP. Restart Chrome with: open -a 'Google Chrome' --args --remote-debugging-port=9222"
112
- elif [ "$count" = "0" ]; then
113
- suggestion="No Chrome detected. Use mode=launch to start Chrome with CDP."
114
- fi
115
-
116
- cat <<EOF
117
- {
118
- "success": true,
119
- "mode": "discover",
120
- "found": $([ "$count" != "0" ] && echo "true" || echo "false"),
121
- "chromeRunning": $chrome_running,
122
- "instances": $found_instances,
123
- "suggestion": $([ -n "$suggestion" ] && echo "\"$suggestion\"" || echo "null")
124
- }
125
- EOF
126
- }
127
-
128
- # ── Mode: Attach ───────────────────────────────────────────
129
-
130
- attach_chrome() {
131
- local port="$1"
132
-
133
- local version_json
134
- version_json=$(check_cdp "$port" 2>/dev/null) || {
135
- cat <<EOF
136
- {
137
- "success": false,
138
- "mode": "attach",
139
- "error": "No CDP endpoint found on port $port",
140
- "suggestion": "Start Chrome with: open -a 'Google Chrome' --args --remote-debugging-port=$port"
141
- }
142
- EOF
143
- return 1
144
- }
145
-
146
- local ws_url browser_version
147
- ws_url=$(echo "$version_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('webSocketDebuggerUrl',''))" 2>/dev/null || echo "")
148
- browser_version=$(echo "$version_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('Browser','unknown'))" 2>/dev/null || echo "unknown")
149
-
150
- # Get list of open tabs/pages
151
- local pages_json
152
- pages_json=$(curl -sf --max-time 2 "http://127.0.0.1:${port}/json" 2>/dev/null || echo "[]")
153
- local page_count
154
- page_count=$(echo "$pages_json" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
155
-
156
- cat <<EOF
157
- {
158
- "success": true,
159
- "mode": "attach",
160
- "connected": true,
161
- "port": $port,
162
- "wsUrl": "$ws_url",
163
- "httpEndpoint": "http://127.0.0.1:$port",
164
- "version": "$browser_version",
165
- "openPages": $page_count,
166
- "message": "Successfully attached to Chrome ($browser_version) on port $port with $page_count open pages"
167
- }
168
- EOF
169
- }
170
-
171
- # ── Mode: Launch ───────────────────────────────────────────
172
-
173
- launch_chrome() {
174
- local port="$1"
175
-
176
- # Check if CDP is already available
177
- if check_cdp "$port" >/dev/null 2>&1; then
178
- log "CDP already available on port $port, attaching..."
179
- attach_chrome "$port"
180
- return
181
- fi
182
-
183
- # Find Chrome binary
184
- local chrome_bin
185
- chrome_bin=$(find_chrome_binary 2>/dev/null) || {
186
- cat <<EOF
187
- {
188
- "success": false,
189
- "mode": "launch",
190
- "error": "Chrome not found. Install Google Chrome."
191
- }
192
- EOF
193
- return 1
194
- }
195
-
196
- # Use the user's default Chrome profile for Live Attach (preserves logins)
197
- local profile_dir
198
- profile_dir=$(get_default_profile)
199
-
200
- if ! is_chrome_running; then
201
- # Chrome not running — launch with CDP on user's profile
202
- log "Launching Chrome with CDP on port $port (user profile)..."
203
- "$chrome_bin" \
204
- --remote-debugging-port="$port" \
205
- --no-first-run \
206
- --no-default-browser-check \
207
- --disable-background-timer-throttling \
208
- --disable-backgrounding-occluded-windows \
209
- --disable-renderer-backgrounding \
210
- &>/dev/null &
211
-
212
- # Wait for CDP to become available
213
- for _ in $(seq 1 20); do
214
- if check_cdp "$port" >/dev/null 2>&1; then
215
- log "Chrome launched with CDP on port $port"
216
- attach_chrome "$port"
217
- return
218
- fi
219
- sleep 0.5
220
- done
221
-
222
- cat <<EOF
223
- {
224
- "success": false,
225
- "mode": "launch",
226
- "error": "Chrome launched but CDP did not become available within 10 seconds"
227
- }
228
- EOF
229
- return 1
230
- fi
231
-
232
- # Chrome IS running without CDP — use alt profile to avoid conflict
233
- local alt_profile="${HOME}/.crewly/chrome-attach-profile"
234
- mkdir -p "$alt_profile"
235
-
236
- log "Chrome is running without CDP. Launching alt instance on port $port..."
237
- "$chrome_bin" \
238
- --remote-debugging-port="$port" \
239
- --user-data-dir="$alt_profile" \
240
- --no-first-run \
241
- --no-default-browser-check \
242
- &>/dev/null &
243
-
244
- for _ in $(seq 1 20); do
245
- if check_cdp "$port" >/dev/null 2>&1; then
246
- log "Alt Chrome launched with CDP on port $port"
247
- attach_chrome "$port"
248
- return
249
- fi
250
- sleep 0.5
251
- done
252
-
253
- cat <<EOF
254
- {
255
- "success": false,
256
- "mode": "launch",
257
- "error": "Failed to launch Chrome with CDP. Try closing Chrome and retrying.",
258
- "suggestion": "Close all Chrome windows, then retry, or run: open -a 'Google Chrome' --args --remote-debugging-port=$port"
259
- }
260
- EOF
261
- }
262
-
263
- # ── Main ───────────────────────────────────────────────────
264
-
265
- case "$MODE" in
266
- discover)
267
- discover_chrome
268
- ;;
269
- attach)
270
- attach_chrome "$PORT"
271
- ;;
272
- launch)
273
- launch_chrome "$PORT"
274
- ;;
275
- *)
276
- echo '{"success": false, "error": "Unknown mode. Use: discover, attach, or launch"}'
277
- exit 1
278
- ;;
279
- esac
@@ -1,140 +0,0 @@
1
- ---
2
- name: VNC Remote Browser Access
3
- description: Launch a virtual display with VNC + noVNC + cloudflared for remote browser viewing and control. Enables human-in-the-loop login/verification when Playwright needs manual interaction.
4
- version: 1.0.0
5
- category: browser
6
- skillType: claude-skill
7
- assignableRoles:
8
- - developer
9
- - qa
10
- - qa-engineer
11
- - fullstack-dev
12
- - backend-developer
13
- - frontend-developer
14
- - generalist
15
- triggers:
16
- - vnc
17
- - remote browser
18
- - browser login
19
- - manual login
20
- - human verification
21
- - captcha
22
- - 2fa
23
- - playwright headful
24
- tags:
25
- - vnc
26
- - browser
27
- - remote-access
28
- - playwright
29
- - noVNC
30
- - cloudflared
31
- execution:
32
- type: script
33
- script:
34
- file: execute.sh
35
- interpreter: bash
36
- timeoutMs: 60000
37
- ---
38
-
39
- # VNC Remote Browser Access
40
-
41
- Bridge macOS Screen Sharing to a public HTTPS URL via noVNC + cloudflared. This lets humans remotely view and control the Mac desktop (and any browser on it) from anywhere — essential when Playwright hits a login page, CAPTCHA, or 2FA prompt.
42
-
43
- The stack: **macOS Screen Sharing** (built-in VNC, port 5900) → **websockify + noVNC** (web client, port 6080) → **cloudflared** (public HTTPS tunnel).
44
-
45
- ## Prerequisites
46
-
47
- **macOS Screen Sharing must be enabled manually** before using this skill:
48
-
49
- 1. Open **System Settings** → **General** → **Sharing**
50
- 2. Turn on **Screen Sharing**
51
- 3. (Optional) Set a VNC password under Screen Sharing options
52
-
53
- This only needs to be done once. Screen Sharing runs as a system service and persists across reboots.
54
-
55
- ## When to Use
56
-
57
- - Playwright hits a login page that requires human credentials
58
- - A website shows a CAPTCHA or anti-bot challenge
59
- - Two-factor authentication (2FA/MFA) requires a code from the user
60
- - You need the user to visually verify something in the browser
61
- - Any browser automation step that needs human-in-the-loop interaction
62
-
63
- ## Parameters
64
-
65
- | Parameter | Required | Description |
66
- |-----------|----------|-------------|
67
- | `action` | Yes | One of: `start`, `stop`, `status`, `get-url` |
68
-
69
- ### Actions
70
-
71
- - **start** — Check Screen Sharing is on, install deps if needed, launch websockify + noVNC + cloudflared. Returns the public URL.
72
- - **stop** — Shut down websockify + cloudflared. Does NOT touch Screen Sharing.
73
- - **status** — Check Screen Sharing (port 5900), websockify, and cloudflared status.
74
- - **get-url** — Retrieve the cloudflared public URL.
75
-
76
- ## Examples
77
-
78
- ### Start and get the public URL
79
-
80
- ```bash
81
- bash config/skills/agent/vnc-browser/execute.sh '{"action":"start"}'
82
- ```
83
-
84
- Output:
85
- ```json
86
- {
87
- "success": true,
88
- "status": "started",
89
- "publicUrl": "https://abc-xyz.trycloudflare.com/vnc.html?autoconnect=true",
90
- "localUrl": "http://localhost:6080/vnc.html?autoconnect=true",
91
- "hint": "Share the publicUrl with the user..."
92
- }
93
- ```
94
-
95
- ### Send the URL to the user via Slack
96
-
97
- ```bash
98
- URL=$(bash config/skills/agent/vnc-browser/execute.sh '{"action":"get-url"}' | jq -r '.publicUrl')
99
- # Then send via reply-slack or report-status
100
- ```
101
-
102
- ### Check status
103
-
104
- ```bash
105
- bash config/skills/agent/vnc-browser/execute.sh '{"action":"status"}'
106
- ```
107
-
108
- ### Stop when done
109
-
110
- ```bash
111
- bash config/skills/agent/vnc-browser/execute.sh '{"action":"stop"}'
112
- ```
113
-
114
- ## Workflow
115
-
116
- 1. **Ensure Screen Sharing is on** (one-time setup)
117
- 2. **Start VNC bridge** → get the public URL
118
- 3. **Launch Playwright** in headful mode (the browser appears on the Mac desktop)
119
- 4. **Navigate** to the page requiring human interaction
120
- 5. **Send the public URL** to the user via Slack
121
- 6. **Wait** for the user to complete the manual step (login, CAPTCHA, etc.)
122
- 7. **Continue automation** once the browser is past the manual step
123
- 8. **Stop VNC bridge** when no longer needed
124
-
125
- ## Technical Details
126
-
127
- - **VNC source**: macOS Screen Sharing on port 5900 (system-managed)
128
- - **Web client**: noVNC on port 6080 via websockify
129
- - **Public tunnel**: cloudflared Quick Tunnel (random `*.trycloudflare.com` subdomain)
130
- - **Security**: Screen Sharing requires macOS user credentials; cloudflared provides HTTPS
131
- - **Dependencies**: websockify (pip), noVNC (GitHub release), cloudflared (Homebrew) — auto-installed on first run
132
- - **PID files**: `~/.crewly/vnc/` for websockify and cloudflared lifecycle
133
- - **Note**: This skill does NOT start or stop macOS Screen Sharing — that is user-managed
134
-
135
- ## Requirements
136
-
137
- - macOS with Screen Sharing enabled
138
- - Homebrew installed
139
- - Python 3 with pip (for websockify)
140
- - Internet access (for cloudflared tunnel and dependency installation)
@@ -1,261 +0,0 @@
1
- #!/bin/bash
2
- # VNC Remote Browser Access — bridge macOS Screen Sharing to a public URL via noVNC
3
- # Allows remote viewing/control of the Mac desktop (and any browser on it) via a web URL.
4
- #
5
- # Architecture:
6
- # macOS Screen Sharing (built-in VNC server on port 5900)
7
- # → websockify (WebSocket bridge + noVNC web server, port 6080)
8
- # → cloudflared (Quick Tunnel → public HTTPS URL)
9
- #
10
- # Prerequisites: macOS Screen Sharing must be enabled manually in System Settings.
11
- #
12
- # Usage: execute.sh '{"action":"start|stop|status|get-url"}'
13
- set -euo pipefail
14
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
- source "${SCRIPT_DIR}/../../_common/lib.sh"
16
-
17
- # ---------------------------------------------------------------------------
18
- # Configuration
19
- # ---------------------------------------------------------------------------
20
- VNC_PORT=5900
21
- NOVNC_PORT=6080
22
- PID_DIR="${HOME}/.crewly/vnc"
23
- NOVNC_DIR="${HOME}/.crewly/novnc"
24
- CLOUDFLARED_LOG="${PID_DIR}/cloudflared.log"
25
-
26
- INPUT="${1:-}"
27
- [ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"action\":\"start|stop|status|get-url\"}'"
28
-
29
- ACTION=$(echo "$INPUT" | jq -r '.action // empty')
30
- require_param "action" "$ACTION"
31
-
32
- mkdir -p "$PID_DIR"
33
-
34
- # ---------------------------------------------------------------------------
35
- # Dependency installation (macOS / Homebrew)
36
- # ---------------------------------------------------------------------------
37
- install_deps() {
38
- local missing=()
39
-
40
- # websockify — WebSocket-to-TCP bridge (Python)
41
- if ! command -v websockify &>/dev/null; then
42
- missing+=("websockify")
43
- echo '{"status":"installing","dep":"websockify"}' >&2
44
- pip3 install websockify 2>/dev/null \
45
- || error_exit "Failed to install websockify. Run: pip3 install websockify"
46
- fi
47
-
48
- # noVNC — HTML5 VNC client
49
- if ! [ -d "$NOVNC_DIR" ] || ! [ -f "$NOVNC_DIR/vnc.html" ]; then
50
- missing+=("novnc")
51
- echo '{"status":"installing","dep":"novnc"}' >&2
52
- local novnc_version="1.5.0"
53
- local novnc_url="https://github.com/novnc/noVNC/archive/refs/tags/v${novnc_version}.tar.gz"
54
- mkdir -p "$NOVNC_DIR"
55
- curl -sL "$novnc_url" | tar -xz -C "$NOVNC_DIR" --strip-components=1
56
- if ! [ -f "$NOVNC_DIR/vnc.html" ]; then
57
- rm -rf "$NOVNC_DIR"
58
- error_exit "Failed to download noVNC web client"
59
- fi
60
- fi
61
-
62
- # cloudflared — Cloudflare Tunnel for public HTTPS URL
63
- if ! command -v cloudflared &>/dev/null; then
64
- missing+=("cloudflared")
65
- echo '{"status":"installing","dep":"cloudflared"}' >&2
66
- brew install cloudflare/cloudflare/cloudflared 2>/dev/null \
67
- || error_exit "Failed to install cloudflared. Run: brew install cloudflare/cloudflare/cloudflared"
68
- fi
69
-
70
- if [ ${#missing[@]} -gt 0 ]; then
71
- echo '{"status":"deps_installed","installed":"'"$(IFS=,; echo "${missing[*]}")"'"}' >&2
72
- fi
73
- }
74
-
75
- # ---------------------------------------------------------------------------
76
- # Helper: check if a service is running via its PID file
77
- # ---------------------------------------------------------------------------
78
- is_running() {
79
- local pid_file="$PID_DIR/${1}.pid"
80
- [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" 2>/dev/null
81
- }
82
-
83
- # ---------------------------------------------------------------------------
84
- # Helper: check if macOS Screen Sharing VNC is listening on port 5900
85
- # ---------------------------------------------------------------------------
86
- check_screen_sharing() {
87
- nc -z localhost "$VNC_PORT" 2>/dev/null
88
- }
89
-
90
- # ---------------------------------------------------------------------------
91
- # start — Launch websockify + cloudflared (requires Screen Sharing on)
92
- # ---------------------------------------------------------------------------
93
- start_vnc() {
94
- # Already running?
95
- if is_running websockify && is_running cloudflared; then
96
- local url=""
97
- [ -f "$PID_DIR/tunnel_url.txt" ] && url=$(cat "$PID_DIR/tunnel_url.txt")
98
- jq -n --arg url "$url" \
99
- '{success: true, status: "already_running", publicUrl: (if $url != "" then ($url + "/vnc.html?autoconnect=true") else null end), localUrl: ("http://localhost:'"$NOVNC_PORT"'/vnc.html?autoconnect=true")}'
100
- return 0
101
- fi
102
-
103
- # Check macOS Screen Sharing is enabled
104
- if ! check_screen_sharing; then
105
- error_exit "macOS Screen Sharing is not running (port $VNC_PORT not listening). Enable it: System Settings → General → Sharing → Screen Sharing → ON"
106
- fi
107
-
108
- # Install missing dependencies
109
- install_deps
110
-
111
- # 1. Start websockify (WebSocket bridge + noVNC web server)
112
- # Bridges noVNC web client (port 6080) to macOS VNC (port 5900)
113
- websockify --web "$NOVNC_DIR" "$NOVNC_PORT" "localhost:$VNC_PORT" \
114
- > "$PID_DIR/websockify.log" 2>&1 &
115
- echo $! > "$PID_DIR/websockify.pid"
116
- sleep 1
117
-
118
- if ! is_running websockify; then
119
- error_exit "websockify failed to start. Check $PID_DIR/websockify.log"
120
- fi
121
-
122
- # 2. Start cloudflared Quick Tunnel (zero-config public URL)
123
- cloudflared tunnel --url "http://localhost:$NOVNC_PORT" \
124
- > "$CLOUDFLARED_LOG" 2>&1 &
125
- echo $! > "$PID_DIR/cloudflared.pid"
126
-
127
- # Wait for cloudflared to print the tunnel URL (up to 30s)
128
- local url=""
129
- for _ in $(seq 1 30); do
130
- url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$CLOUDFLARED_LOG" 2>/dev/null | head -1 || true)
131
- [ -n "$url" ] && break
132
- sleep 1
133
- done
134
-
135
- if [ -n "$url" ]; then
136
- echo "$url" > "$PID_DIR/tunnel_url.txt"
137
- jq -n --arg url "$url" \
138
- '{success: true, status: "started", publicUrl: ($url + "/vnc.html?autoconnect=true"), localUrl: ("http://localhost:'"$NOVNC_PORT"'/vnc.html?autoconnect=true"), hint: "Share the publicUrl with the user. They will see the Mac desktop and can interact with the browser."}'
139
- else
140
- jq -n \
141
- '{success: true, status: "started_no_tunnel", publicUrl: null, localUrl: ("http://localhost:'"$NOVNC_PORT"'/vnc.html?autoconnect=true"), hint: "Cloudflared URL not ready yet. Use get-url action to retrieve it later."}'
142
- fi
143
- }
144
-
145
- # ---------------------------------------------------------------------------
146
- # stop — Tear down websockify + cloudflared (does NOT touch Screen Sharing)
147
- # ---------------------------------------------------------------------------
148
- stop_vnc() {
149
- local stopped=0
150
-
151
- for service in cloudflared websockify; do
152
- local pid_file="$PID_DIR/${service}.pid"
153
- if [ -f "$pid_file" ]; then
154
- local pid
155
- pid=$(cat "$pid_file")
156
- if kill -0 "$pid" 2>/dev/null; then
157
- kill "$pid" 2>/dev/null || true
158
- stopped=$((stopped + 1))
159
- fi
160
- rm -f "$pid_file"
161
- fi
162
- done
163
-
164
- rm -f "$PID_DIR/tunnel_url.txt"
165
-
166
- jq -n --argjson stopped "$stopped" \
167
- '{success: true, status: "stopped", stoppedServices: $stopped, note: "macOS Screen Sharing was not touched"}'
168
- }
169
-
170
- # ---------------------------------------------------------------------------
171
- # status — Check which services are running
172
- # ---------------------------------------------------------------------------
173
- status_vnc() {
174
- local services='[]'
175
- local all_running=true
176
-
177
- # Check macOS Screen Sharing (port 5900)
178
- local screen_sharing=false
179
- if check_screen_sharing; then
180
- screen_sharing=true
181
- else
182
- all_running=false
183
- fi
184
- services=$(echo "$services" | jq \
185
- --arg name "screen-sharing" \
186
- --argjson running "$screen_sharing" \
187
- --arg pid "system" \
188
- '. + [{name: $name, running: $running, pid: $pid}]')
189
-
190
- # Check managed services
191
- for service in websockify cloudflared; do
192
- local pid_file="$PID_DIR/${service}.pid"
193
- local running=false
194
- local pid=""
195
-
196
- if [ -f "$pid_file" ]; then
197
- pid=$(cat "$pid_file")
198
- if kill -0 "$pid" 2>/dev/null; then
199
- running=true
200
- fi
201
- fi
202
-
203
- [ "$running" = false ] && all_running=false
204
-
205
- services=$(echo "$services" | jq \
206
- --arg name "$service" \
207
- --argjson running "$running" \
208
- --arg pid "$pid" \
209
- '. + [{name: $name, running: $running, pid: $pid}]')
210
- done
211
-
212
- local url=""
213
- [ -f "$PID_DIR/tunnel_url.txt" ] && url=$(cat "$PID_DIR/tunnel_url.txt")
214
-
215
- jq -n \
216
- --argjson allRunning "$all_running" \
217
- --argjson services "$services" \
218
- --arg publicUrl "${url:-}" \
219
- '{success: true, allRunning: $allRunning, publicUrl: (if $publicUrl != "" then ($publicUrl + "/vnc.html?autoconnect=true") else null end), services: $services}'
220
- }
221
-
222
- # ---------------------------------------------------------------------------
223
- # get-url — Retrieve the public tunnel URL
224
- # ---------------------------------------------------------------------------
225
- get_url() {
226
- # Try saved URL
227
- if [ -f "$PID_DIR/tunnel_url.txt" ]; then
228
- local url
229
- url=$(cat "$PID_DIR/tunnel_url.txt")
230
- if [ -n "$url" ]; then
231
- jq -n --arg url "$url" \
232
- '{success: true, publicUrl: ($url + "/vnc.html?autoconnect=true"), localUrl: ("http://localhost:'"$NOVNC_PORT"'/vnc.html?autoconnect=true")}'
233
- return 0
234
- fi
235
- fi
236
-
237
- # Try parsing from cloudflared log
238
- if [ -f "$CLOUDFLARED_LOG" ]; then
239
- local url
240
- url=$(grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com' "$CLOUDFLARED_LOG" 2>/dev/null | head -1 || true)
241
- if [ -n "$url" ]; then
242
- echo "$url" > "$PID_DIR/tunnel_url.txt"
243
- jq -n --arg url "$url" \
244
- '{success: true, publicUrl: ($url + "/vnc.html?autoconnect=true"), localUrl: ("http://localhost:'"$NOVNC_PORT"'/vnc.html?autoconnect=true")}'
245
- return 0
246
- fi
247
- fi
248
-
249
- error_exit "No tunnel URL available. Is VNC started? Try: execute.sh '{\"action\":\"start\"}'"
250
- }
251
-
252
- # ---------------------------------------------------------------------------
253
- # Dispatch
254
- # ---------------------------------------------------------------------------
255
- case "$ACTION" in
256
- start) start_vnc ;;
257
- stop) stop_vnc ;;
258
- status) status_vnc ;;
259
- get-url) get_url ;;
260
- *) error_exit "Unknown action: $ACTION. Valid actions: start, stop, status, get-url" ;;
261
- esac