@thinkrun/cli 0.1.27
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/README.md +349 -0
- package/dist/bin/thinkrun.d.ts +6 -0
- package/dist/bin/thinkrun.d.ts.map +1 -0
- package/dist/bin/thinkrun.js +124 -0
- package/dist/bin/thinkrun.js.map +1 -0
- package/dist/scripts/browse.sh +1107 -0
- package/dist/src/adapters/cloud.d.ts +79 -0
- package/dist/src/adapters/cloud.d.ts.map +1 -0
- package/dist/src/adapters/cloud.js +637 -0
- package/dist/src/adapters/cloud.js.map +1 -0
- package/dist/src/adapters/index.d.ts +47 -0
- package/dist/src/adapters/index.d.ts.map +1 -0
- package/dist/src/adapters/index.js +211 -0
- package/dist/src/adapters/index.js.map +1 -0
- package/dist/src/adapters/local-command-retry.d.ts +12 -0
- package/dist/src/adapters/local-command-retry.d.ts.map +1 -0
- package/dist/src/adapters/local-command-retry.js +224 -0
- package/dist/src/adapters/local-command-retry.js.map +1 -0
- package/dist/src/adapters/local.d.ts +136 -0
- package/dist/src/adapters/local.d.ts.map +1 -0
- package/dist/src/adapters/local.js +1273 -0
- package/dist/src/adapters/local.js.map +1 -0
- package/dist/src/adapters/types.d.ts +45 -0
- package/dist/src/adapters/types.d.ts.map +1 -0
- package/dist/src/adapters/types.js +6 -0
- package/dist/src/adapters/types.js.map +1 -0
- package/dist/src/commands/actions.d.ts +135 -0
- package/dist/src/commands/actions.d.ts.map +1 -0
- package/dist/src/commands/actions.js +2207 -0
- package/dist/src/commands/actions.js.map +1 -0
- package/dist/src/commands/agent-init.d.ts +16 -0
- package/dist/src/commands/agent-init.d.ts.map +1 -0
- package/dist/src/commands/agent-init.js +222 -0
- package/dist/src/commands/agent-init.js.map +1 -0
- package/dist/src/commands/analyze.d.ts +11 -0
- package/dist/src/commands/analyze.d.ts.map +1 -0
- package/dist/src/commands/analyze.js +238 -0
- package/dist/src/commands/analyze.js.map +1 -0
- package/dist/src/commands/cache.d.ts +6 -0
- package/dist/src/commands/cache.d.ts.map +1 -0
- package/dist/src/commands/cache.js +147 -0
- package/dist/src/commands/cache.js.map +1 -0
- package/dist/src/commands/cloud.d.ts +6 -0
- package/dist/src/commands/cloud.d.ts.map +1 -0
- package/dist/src/commands/cloud.js +332 -0
- package/dist/src/commands/cloud.js.map +1 -0
- package/dist/src/commands/config.d.ts +7 -0
- package/dist/src/commands/config.d.ts.map +1 -0
- package/dist/src/commands/config.js +208 -0
- package/dist/src/commands/config.js.map +1 -0
- package/dist/src/commands/doctor.d.ts +127 -0
- package/dist/src/commands/doctor.d.ts.map +1 -0
- package/dist/src/commands/doctor.js +684 -0
- package/dist/src/commands/doctor.js.map +1 -0
- package/dist/src/commands/evaluate-helpers.d.ts +6 -0
- package/dist/src/commands/evaluate-helpers.d.ts.map +1 -0
- package/dist/src/commands/evaluate-helpers.js +13 -0
- package/dist/src/commands/evaluate-helpers.js.map +1 -0
- package/dist/src/commands/install.d.ts +118 -0
- package/dist/src/commands/install.d.ts.map +1 -0
- package/dist/src/commands/install.js +975 -0
- package/dist/src/commands/install.js.map +1 -0
- package/dist/src/commands/release.d.ts +7 -0
- package/dist/src/commands/release.d.ts.map +1 -0
- package/dist/src/commands/release.js +123 -0
- package/dist/src/commands/release.js.map +1 -0
- package/dist/src/commands/reset-connection.d.ts +17 -0
- package/dist/src/commands/reset-connection.d.ts.map +1 -0
- package/dist/src/commands/reset-connection.js +141 -0
- package/dist/src/commands/reset-connection.js.map +1 -0
- package/dist/src/commands/session-debug.d.ts +23 -0
- package/dist/src/commands/session-debug.d.ts.map +1 -0
- package/dist/src/commands/session-debug.js +267 -0
- package/dist/src/commands/session-debug.js.map +1 -0
- package/dist/src/commands/setup.d.ts +53 -0
- package/dist/src/commands/setup.d.ts.map +1 -0
- package/dist/src/commands/setup.js +249 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/config/store.d.ts +39 -0
- package/dist/src/config/store.d.ts.map +1 -0
- package/dist/src/config/store.js +290 -0
- package/dist/src/config/store.js.map +1 -0
- package/dist/src/daemon/access.d.ts +53 -0
- package/dist/src/daemon/access.d.ts.map +1 -0
- package/dist/src/daemon/access.js +87 -0
- package/dist/src/daemon/access.js.map +1 -0
- package/dist/src/daemon/bridge-envelope.d.ts +96 -0
- package/dist/src/daemon/bridge-envelope.d.ts.map +1 -0
- package/dist/src/daemon/bridge-envelope.js +235 -0
- package/dist/src/daemon/bridge-envelope.js.map +1 -0
- package/dist/src/daemon/utils.d.ts +43 -0
- package/dist/src/daemon/utils.d.ts.map +1 -0
- package/dist/src/daemon/utils.js +134 -0
- package/dist/src/daemon/utils.js.map +1 -0
- package/dist/src/errors.d.ts +60 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +87 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/local-bridge-timing.d.ts +31 -0
- package/dist/src/local-bridge-timing.d.ts.map +1 -0
- package/dist/src/local-bridge-timing.js +41 -0
- package/dist/src/local-bridge-timing.js.map +1 -0
- package/dist/src/obstacle-recovery/classify-script.d.ts +16 -0
- package/dist/src/obstacle-recovery/classify-script.d.ts.map +1 -0
- package/dist/src/obstacle-recovery/classify-script.js +53 -0
- package/dist/src/obstacle-recovery/classify-script.js.map +1 -0
- package/dist/src/obstacle-recovery/obstacle-classifier.d.ts +21 -0
- package/dist/src/obstacle-recovery/obstacle-classifier.d.ts.map +1 -0
- package/dist/src/obstacle-recovery/obstacle-classifier.js +37 -0
- package/dist/src/obstacle-recovery/obstacle-classifier.js.map +1 -0
- package/dist/src/obstacle-recovery/state-fingerprint.d.ts +26 -0
- package/dist/src/obstacle-recovery/state-fingerprint.d.ts.map +1 -0
- package/dist/src/obstacle-recovery/state-fingerprint.js +85 -0
- package/dist/src/obstacle-recovery/state-fingerprint.js.map +1 -0
- package/dist/src/obstacle-recovery/types.d.ts +44 -0
- package/dist/src/obstacle-recovery/types.d.ts.map +1 -0
- package/dist/src/obstacle-recovery/types.js +16 -0
- package/dist/src/obstacle-recovery/types.js.map +1 -0
- package/dist/src/output/formatter.d.ts +55 -0
- package/dist/src/output/formatter.d.ts.map +1 -0
- package/dist/src/output/formatter.js +55 -0
- package/dist/src/output/formatter.js.map +1 -0
- package/dist/src/output/mode.d.ts +11 -0
- package/dist/src/output/mode.d.ts.map +1 -0
- package/dist/src/output/mode.js +16 -0
- package/dist/src/output/mode.js.map +1 -0
- package/dist/src/protected-flow/detector.d.ts +26 -0
- package/dist/src/protected-flow/detector.d.ts.map +1 -0
- package/dist/src/protected-flow/detector.js +75 -0
- package/dist/src/protected-flow/detector.js.map +1 -0
- package/dist/src/protected-flow/types.d.ts +24 -0
- package/dist/src/protected-flow/types.d.ts.map +1 -0
- package/dist/src/protected-flow/types.js +28 -0
- package/dist/src/protected-flow/types.js.map +1 -0
- package/dist/src/session/agent-identity.d.ts +65 -0
- package/dist/src/session/agent-identity.d.ts.map +1 -0
- package/dist/src/session/agent-identity.js +133 -0
- package/dist/src/session/agent-identity.js.map +1 -0
- package/dist/src/session/cli-session-sync.d.ts +72 -0
- package/dist/src/session/cli-session-sync.d.ts.map +1 -0
- package/dist/src/session/cli-session-sync.js +244 -0
- package/dist/src/session/cli-session-sync.js.map +1 -0
- package/dist/src/session/context.d.ts +24 -0
- package/dist/src/session/context.d.ts.map +1 -0
- package/dist/src/session/context.js +165 -0
- package/dist/src/session/context.js.map +1 -0
- package/dist/src/session/continuity.d.ts +33 -0
- package/dist/src/session/continuity.d.ts.map +1 -0
- package/dist/src/session/continuity.js +179 -0
- package/dist/src/session/continuity.js.map +1 -0
- package/dist/src/session/errors.d.ts +9 -0
- package/dist/src/session/errors.d.ts.map +1 -0
- package/dist/src/session/errors.js +31 -0
- package/dist/src/session/errors.js.map +1 -0
- package/dist/src/session/local-continuity.d.ts +16 -0
- package/dist/src/session/local-continuity.d.ts.map +1 -0
- package/dist/src/session/local-continuity.js +146 -0
- package/dist/src/session/local-continuity.js.map +1 -0
- package/dist/src/session/signal-handler.d.ts +24 -0
- package/dist/src/session/signal-handler.d.ts.map +1 -0
- package/dist/src/session/signal-handler.js +35 -0
- package/dist/src/session/signal-handler.js.map +1 -0
- package/dist/src/shared/local-recovery-policy.d.ts +40 -0
- package/dist/src/shared/local-recovery-policy.d.ts.map +1 -0
- package/dist/src/shared/local-recovery-policy.js +59 -0
- package/dist/src/shared/local-recovery-policy.js.map +1 -0
- package/dist/src/shared/recovery-state.d.ts +3 -0
- package/dist/src/shared/recovery-state.d.ts.map +1 -0
- package/dist/src/shared/recovery-state.js +9 -0
- package/dist/src/shared/recovery-state.js.map +1 -0
- package/dist/src/types.d.ts +131 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +50 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +147 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/src/working-location.d.ts +107 -0
- package/dist/src/working-location.d.ts.map +1 -0
- package/dist/src/working-location.js +651 -0
- package/dist/src/working-location.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,1107 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# browse — Portable browser automation for AI agent brains
|
|
3
|
+
#
|
|
4
|
+
# Modes:
|
|
5
|
+
# LOCAL: Controls your Chrome browser via the native messaging host
|
|
6
|
+
# Port auto-discovered from ~/.thinkbrowse/port (written by native host on startup)
|
|
7
|
+
# CLOUD: Controls a cloud Playwright browser via ThinkRun REST API
|
|
8
|
+
#
|
|
9
|
+
# Mode detection (in order):
|
|
10
|
+
# 1. THINKRUN_LOCAL=true → local mode
|
|
11
|
+
# 2. Native host responding on discovered port → local mode
|
|
12
|
+
# 3. THINKRUN_API_KEY or config file → cloud mode
|
|
13
|
+
#
|
|
14
|
+
# Deps: curl, jq
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# browse session-create Create session, print ID
|
|
18
|
+
# browse session-artifacts <sid> List cloud session artifacts
|
|
19
|
+
# browse artifact-download <id> [out] Download artifact by ID (cloud mode)
|
|
20
|
+
# browse goto <sid> <url> Navigate to URL
|
|
21
|
+
# browse click <sid> <selector> Click element
|
|
22
|
+
# browse extract <sid> [text|html] Extract page content
|
|
23
|
+
# browse session-delete <sid> Close session
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
|
|
29
|
+
# --- Config ---
|
|
30
|
+
CONFIG_FILE="$HOME/.config/thinkrun/config.json"
|
|
31
|
+
PORT_DISCOVERY_FILE="$HOME/.thinkbrowse/port"
|
|
32
|
+
TAB_DIR="/tmp/thinkrun-tabs"
|
|
33
|
+
LOCAL_HOST="${THINKRUN_LOCAL_HOST:-127.0.0.1}"
|
|
34
|
+
if [ "$LOCAL_HOST" = "localhost" ]; then
|
|
35
|
+
echo "WARN: localhost is non-deterministic (IPv4/IPv6); using 127.0.0.1 for ThinkRun bridge." >&2
|
|
36
|
+
LOCAL_HOST="127.0.0.1"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Port discovery: THINKRUN_LOCAL_PORT > THINKRUN_BRIDGE_PORT (legacy) > discovery file > default 3012
|
|
40
|
+
resolve_local_port() {
|
|
41
|
+
if [ -n "${THINKRUN_LOCAL_PORT:-}" ]; then
|
|
42
|
+
echo "$THINKRUN_LOCAL_PORT"
|
|
43
|
+
return
|
|
44
|
+
fi
|
|
45
|
+
if [ -n "${THINKRUN_BRIDGE_PORT:-}" ]; then
|
|
46
|
+
echo "$THINKRUN_BRIDGE_PORT"
|
|
47
|
+
return
|
|
48
|
+
fi
|
|
49
|
+
if [ -f "$PORT_DISCOVERY_FILE" ]; then
|
|
50
|
+
local port
|
|
51
|
+
port=$(cat "$PORT_DISCOVERY_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
52
|
+
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -gt 0 ] && [ "$port" -le 65535 ]; then
|
|
53
|
+
echo "$port"
|
|
54
|
+
return
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
echo "3012"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
LOCAL_PORT=$(resolve_local_port)
|
|
61
|
+
LOCAL_URL="http://$LOCAL_HOST:$LOCAL_PORT"
|
|
62
|
+
|
|
63
|
+
# Local-bridge timing contract. Keep these values aligned with
|
|
64
|
+
# packages/cli/src/local-bridge-timing.ts when changing retry behavior.
|
|
65
|
+
LOCAL_SAFE_READ_RETRY_MAX_ATTEMPTS="${THINKRUN_LOCAL_SAFE_READ_RETRY_MAX_ATTEMPTS:-3}"
|
|
66
|
+
LOCAL_SAFE_READ_RETRY_INITIAL_DELAY_S="${THINKRUN_LOCAL_SAFE_READ_RETRY_INITIAL_DELAY_S:-1}"
|
|
67
|
+
LOCAL_SAFE_READ_RETRY_BACKOFF_MULTIPLIER="${THINKRUN_LOCAL_SAFE_READ_RETRY_BACKOFF_MULTIPLIER:-2}"
|
|
68
|
+
|
|
69
|
+
is_thinkbrowse_health_response() {
|
|
70
|
+
local health_json="$1"
|
|
71
|
+
echo "$health_json" | jq -e '
|
|
72
|
+
.success == true
|
|
73
|
+
and (.data | type == "object")
|
|
74
|
+
and (.data.status == "ok")
|
|
75
|
+
and (.data.service == "thinkbrowse-native-host")
|
|
76
|
+
and ((.data.extensionConnected | type) == "boolean")
|
|
77
|
+
' >/dev/null 2>&1
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
probe_local_bridge_health() {
|
|
81
|
+
local health_json
|
|
82
|
+
health_json=$(curl -4 -s --max-time 2 "$LOCAL_URL/health" 2>/dev/null || true)
|
|
83
|
+
if [ -z "$health_json" ]; then
|
|
84
|
+
return 1
|
|
85
|
+
fi
|
|
86
|
+
is_thinkbrowse_health_response "$health_json"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# --- Parse --mode flag before mode detection ---
|
|
90
|
+
MODE_OVERRIDE=""
|
|
91
|
+
ARGS=()
|
|
92
|
+
while [[ $# -gt 0 ]]; do
|
|
93
|
+
case "$1" in
|
|
94
|
+
--mode)
|
|
95
|
+
MODE_OVERRIDE="${2:-}"
|
|
96
|
+
shift 2
|
|
97
|
+
;;
|
|
98
|
+
--mode=*)
|
|
99
|
+
MODE_OVERRIDE="${1#--mode=}"
|
|
100
|
+
shift
|
|
101
|
+
;;
|
|
102
|
+
*)
|
|
103
|
+
ARGS+=("$1")
|
|
104
|
+
shift
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
done
|
|
108
|
+
# Restore positional parameters without --mode flags
|
|
109
|
+
set -- "${ARGS[@]+"${ARGS[@]}"}"
|
|
110
|
+
|
|
111
|
+
# --- Mode Detection ---
|
|
112
|
+
MODE=""
|
|
113
|
+
|
|
114
|
+
if [ -n "$MODE_OVERRIDE" ]; then
|
|
115
|
+
case "$MODE_OVERRIDE" in
|
|
116
|
+
native|local) MODE="local" ;; # native is now an alias for local
|
|
117
|
+
cloud) MODE="cloud" ;;
|
|
118
|
+
*)
|
|
119
|
+
echo "ERROR: Unknown --mode value '$MODE_OVERRIDE' (must be local or cloud)" >&2
|
|
120
|
+
exit 1
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
elif [ "${THINKRUN_LOCAL:-}" = "true" ]; then
|
|
124
|
+
MODE="local"
|
|
125
|
+
elif probe_local_bridge_health; then
|
|
126
|
+
MODE="local"
|
|
127
|
+
else
|
|
128
|
+
# Try cloud mode
|
|
129
|
+
API_KEY=""
|
|
130
|
+
if [ -n "${THINKRUN_API_KEY:-}" ]; then
|
|
131
|
+
API_KEY="$THINKRUN_API_KEY"
|
|
132
|
+
elif [ -f "$CONFIG_FILE" ]; then
|
|
133
|
+
API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if [ -n "${API_KEY:-}" ]; then
|
|
137
|
+
MODE="cloud"
|
|
138
|
+
else
|
|
139
|
+
echo "ERROR: No browser automation endpoint or API key found." >&2
|
|
140
|
+
echo " Local mode: Open Chrome/Helium (Chromium) with ThinkRun extension + native host installed" >&2
|
|
141
|
+
echo " Cloud mode: Set THINKRUN_API_KEY or create $CONFIG_FILE" >&2
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# --- Cloud config ---
|
|
147
|
+
if [ "$MODE" = "cloud" ]; then
|
|
148
|
+
# Resolve API key for explicit --mode cloud and auto-detected cloud mode.
|
|
149
|
+
if [ -n "${THINKRUN_API_KEY:-}" ]; then
|
|
150
|
+
API_KEY="$THINKRUN_API_KEY"
|
|
151
|
+
elif [ -f "$CONFIG_FILE" ]; then
|
|
152
|
+
API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
153
|
+
else
|
|
154
|
+
API_KEY=""
|
|
155
|
+
fi
|
|
156
|
+
if [ -z "${API_KEY:-}" ]; then
|
|
157
|
+
echo "ERROR: Cloud mode requires THINKRUN_API_KEY or $CONFIG_FILE" >&2
|
|
158
|
+
exit 1
|
|
159
|
+
fi
|
|
160
|
+
# Read apiUrl from config file, fall back to env var or default
|
|
161
|
+
CONFIG_API_URL=""
|
|
162
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
163
|
+
CONFIG_API_URL=$(jq -r '.apiUrl // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
164
|
+
fi
|
|
165
|
+
BASE="${THINKRUN_URL:-${CONFIG_API_URL:-https://api.thinkbrowse.io}}"
|
|
166
|
+
H=(-H "X-API-Key: $API_KEY" -H "Content-Type: application/json")
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# --- Local session sync helpers ---
|
|
170
|
+
LOCAL_SYNC_AUTH_INITIALIZED="false"
|
|
171
|
+
LOCAL_SYNC_API_KEY=""
|
|
172
|
+
LOCAL_SYNC_BASE=""
|
|
173
|
+
|
|
174
|
+
init_local_sync_auth() {
|
|
175
|
+
if [ "$LOCAL_SYNC_AUTH_INITIALIZED" = "true" ]; then
|
|
176
|
+
return
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
local cfg_api_key=""
|
|
180
|
+
local cfg_api_url=""
|
|
181
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
182
|
+
cfg_api_key=$(jq -r '.apiKey // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
183
|
+
cfg_api_url=$(jq -r '.apiUrl // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
LOCAL_SYNC_API_KEY="${THINKRUN_API_KEY:-$cfg_api_key}"
|
|
187
|
+
LOCAL_SYNC_BASE="${THINKRUN_URL:-${cfg_api_url:-https://api.thinkbrowse.io}}"
|
|
188
|
+
LOCAL_SYNC_AUTH_INITIALIZED="true"
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
can_sync_local_session() {
|
|
192
|
+
local sid="$1"
|
|
193
|
+
[ "$MODE" = "local" ] || return 1
|
|
194
|
+
[[ "$sid" == local-* ]] && return 1
|
|
195
|
+
init_local_sync_auth
|
|
196
|
+
[ -n "${LOCAL_SYNC_API_KEY:-}" ]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
sync_local_action_event() {
|
|
200
|
+
local sid="$1"
|
|
201
|
+
local action_type="$2"
|
|
202
|
+
local success="$3"
|
|
203
|
+
local details_json="${4:-{}}"
|
|
204
|
+
local error_msg="${5:-}"
|
|
205
|
+
local artifact_b64="${6:-}"
|
|
206
|
+
|
|
207
|
+
can_sync_local_session "$sid" || return 0
|
|
208
|
+
|
|
209
|
+
local payload
|
|
210
|
+
payload=$(jq -cn \
|
|
211
|
+
--arg type "$action_type" \
|
|
212
|
+
--arg success "$success" \
|
|
213
|
+
--arg details_raw "$details_json" \
|
|
214
|
+
--arg error "$error_msg" \
|
|
215
|
+
--arg artifact "$artifact_b64" \
|
|
216
|
+
'{
|
|
217
|
+
type: $type,
|
|
218
|
+
success: ($success == "true"),
|
|
219
|
+
details: ($details_raw | fromjson? // {})
|
|
220
|
+
}
|
|
221
|
+
+ (if ($error|length) > 0 then { error: $error } else {} end)
|
|
222
|
+
+ (if ($artifact|length) > 0 then { artifactBase64: $artifact, artifactMimeType: "image/png" } else {} end)
|
|
223
|
+
')
|
|
224
|
+
|
|
225
|
+
curl -s \
|
|
226
|
+
-H "X-API-Key: $LOCAL_SYNC_API_KEY" \
|
|
227
|
+
-H "Content-Type: application/json" \
|
|
228
|
+
-X POST "$LOCAL_SYNC_BASE/api/sessions/$sid/local-sync/action" \
|
|
229
|
+
-d "$payload" >/dev/null 2>&1 || true
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
maybe_sync_local_command() {
|
|
233
|
+
local sid="$1"
|
|
234
|
+
local http_code="$2"
|
|
235
|
+
local body="$3"
|
|
236
|
+
shift 3
|
|
237
|
+
|
|
238
|
+
can_sync_local_session "$sid" || return 0
|
|
239
|
+
|
|
240
|
+
local method="POST"
|
|
241
|
+
local req_data=""
|
|
242
|
+
local req_url=""
|
|
243
|
+
local prev=""
|
|
244
|
+
for arg in "$@"; do
|
|
245
|
+
if [ "$prev" = "-X" ]; then
|
|
246
|
+
method="$arg"
|
|
247
|
+
prev=""
|
|
248
|
+
continue
|
|
249
|
+
fi
|
|
250
|
+
if [ "$prev" = "-d" ]; then
|
|
251
|
+
req_data="$arg"
|
|
252
|
+
prev=""
|
|
253
|
+
continue
|
|
254
|
+
fi
|
|
255
|
+
case "$arg" in
|
|
256
|
+
-X|-d)
|
|
257
|
+
prev="$arg"
|
|
258
|
+
;;
|
|
259
|
+
http://*|https://*)
|
|
260
|
+
req_url="$arg"
|
|
261
|
+
;;
|
|
262
|
+
esac
|
|
263
|
+
done
|
|
264
|
+
|
|
265
|
+
[ -n "$req_url" ] || return 0
|
|
266
|
+
[ "$method" = "POST" ] || return 0
|
|
267
|
+
|
|
268
|
+
local endpoint="${req_url##*/api/}"
|
|
269
|
+
local action_type=""
|
|
270
|
+
case "$endpoint" in
|
|
271
|
+
navigate) action_type="navigate" ;;
|
|
272
|
+
click) action_type="click" ;;
|
|
273
|
+
fill) action_type="fill" ;;
|
|
274
|
+
type) action_type="type" ;;
|
|
275
|
+
press) action_type="press" ;;
|
|
276
|
+
hover) action_type="hover" ;;
|
|
277
|
+
select) action_type="select" ;;
|
|
278
|
+
wait) action_type="wait" ;;
|
|
279
|
+
wait-for-text) action_type="wait" ;;
|
|
280
|
+
evaluate) action_type="evaluate" ;;
|
|
281
|
+
extract) action_type="extract" ;;
|
|
282
|
+
snapshot) action_type="snapshot" ;;
|
|
283
|
+
screenshot) action_type="screenshot" ;;
|
|
284
|
+
scroll) action_type="scroll" ;;
|
|
285
|
+
go-back) action_type="go-back" ;;
|
|
286
|
+
go-forward) action_type="go-forward" ;;
|
|
287
|
+
*) return 0 ;;
|
|
288
|
+
esac
|
|
289
|
+
|
|
290
|
+
local details_json="{}"
|
|
291
|
+
if [ -n "$req_data" ]; then
|
|
292
|
+
details_json=$(printf '%s' "$req_data" | jq -c '.' 2>/dev/null || echo '{}')
|
|
293
|
+
fi
|
|
294
|
+
details_json=$(jq -cn --arg endpoint "$endpoint" --arg details_raw "$details_json" '{ endpoint: $endpoint } + ($details_raw | fromjson? // {})')
|
|
295
|
+
|
|
296
|
+
local success_bool error_msg artifact_b64
|
|
297
|
+
success_bool=$(echo "$body" | jq -r 'if (.success == false) then "false" else "true" end' 2>/dev/null || echo "true")
|
|
298
|
+
error_msg=$(echo "$body" | jq -r '.error // .message // empty' 2>/dev/null || echo "")
|
|
299
|
+
artifact_b64=""
|
|
300
|
+
if [ "$action_type" = "screenshot" ]; then
|
|
301
|
+
artifact_b64=$(echo "$body" | jq -r '.data.screenshot // .screenshot // empty' 2>/dev/null || echo "")
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
sync_local_action_event "$sid" "$action_type" "$success_bool" "$details_json" "$error_msg" "$artifact_b64"
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
# --- Local helpers ---
|
|
308
|
+
|
|
309
|
+
# Check native host is reachable
|
|
310
|
+
ensure_local() {
|
|
311
|
+
local health_json
|
|
312
|
+
health_json=$(curl -4 -s --max-time 2 "$LOCAL_URL/health" 2>/dev/null || true)
|
|
313
|
+
|
|
314
|
+
if [ -z "$health_json" ]; then
|
|
315
|
+
echo "ERROR: Native host not responding on $LOCAL_URL" >&2
|
|
316
|
+
echo " Make sure Chrome is open with the ThinkRun extension installed." >&2
|
|
317
|
+
exit 1
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
if ! is_thinkbrowse_health_response "$health_json"; then
|
|
321
|
+
echo "ERROR: Service on $LOCAL_URL is not the ThinkRun native host." >&2
|
|
322
|
+
echo " Received /health payload: $health_json" >&2
|
|
323
|
+
echo " This usually means another local service is using port $LOCAL_PORT" >&2
|
|
324
|
+
echo " or localhost/IPv6 resolved to a different process." >&2
|
|
325
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
326
|
+
echo " Listening processes on :$LOCAL_PORT:" >&2
|
|
327
|
+
lsof -nP -iTCP:"$LOCAL_PORT" -sTCP:LISTEN >&2 || true
|
|
328
|
+
fi
|
|
329
|
+
echo " Fix: keep ThinkRun on $LOCAL_HOST:$LOCAL_PORT, move conflicting services," >&2
|
|
330
|
+
echo " or override host/port with THINKRUN_LOCAL_HOST / THINKRUN_LOCAL_PORT." >&2
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
# Save tab ID for a local session
|
|
336
|
+
save_tab_id() {
|
|
337
|
+
local sid="$1" tab_id="$2"
|
|
338
|
+
mkdir -p "$TAB_DIR"
|
|
339
|
+
echo "$tab_id" > "$TAB_DIR/$sid"
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# Load tab ID for a local session
|
|
343
|
+
load_tab_id() {
|
|
344
|
+
local sid="$1"
|
|
345
|
+
local tab_file="$TAB_DIR/$sid"
|
|
346
|
+
if [ -f "$tab_file" ]; then
|
|
347
|
+
cat "$tab_file"
|
|
348
|
+
return
|
|
349
|
+
fi
|
|
350
|
+
# Fallback: if sid is a numeric Chrome tab ID, verify it exists and pin it
|
|
351
|
+
if [[ "$sid" =~ ^[0-9]+$ ]]; then
|
|
352
|
+
local tabs_json
|
|
353
|
+
tabs_json=$(curl -sf --max-time 5 "http://${LOCAL_HOST}:${LOCAL_PORT}/api/tabs" -H "Accept: application/json" 2>/dev/null)
|
|
354
|
+
if [ -n "$tabs_json" ]; then
|
|
355
|
+
local match
|
|
356
|
+
match=$(echo "$tabs_json" | jq -r --arg id "$sid" '.data.tabs[] | select(.id == ($id | tonumber)) | .id' 2>/dev/null)
|
|
357
|
+
if [ -n "$match" ] && [ "$match" != "null" ]; then
|
|
358
|
+
# Auto-pin: record the raw numeric tab ID as its own session mapping
|
|
359
|
+
save_tab_id "$sid" "$match"
|
|
360
|
+
echo "$match"
|
|
361
|
+
return
|
|
362
|
+
fi
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
echo "ERROR: No tab found for session $sid" >&2
|
|
366
|
+
exit 1
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
# Remove tab ID file for a local session
|
|
370
|
+
remove_tab_id() {
|
|
371
|
+
local sid="$1"
|
|
372
|
+
rm -f "$TAB_DIR/$sid"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
local_retry_profile() {
|
|
376
|
+
local endpoint="$1"
|
|
377
|
+
case "$endpoint" in
|
|
378
|
+
snapshot|screenshot|extract|wait|wait-for-text|console|network|url|title|html)
|
|
379
|
+
echo "safe_same_bridge"
|
|
380
|
+
;;
|
|
381
|
+
navigate|click|fill|type|press|hover|select|evaluate|scroll|go-back|go-forward|dialog|clear-logs)
|
|
382
|
+
echo "unknown_outcome"
|
|
383
|
+
;;
|
|
384
|
+
*)
|
|
385
|
+
echo "unknown_outcome"
|
|
386
|
+
;;
|
|
387
|
+
esac
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
local_retry_endpoint_from_args() {
|
|
391
|
+
local req_url=""
|
|
392
|
+
for arg in "$@"; do
|
|
393
|
+
case "$arg" in
|
|
394
|
+
http://*|https://*)
|
|
395
|
+
req_url="$arg"
|
|
396
|
+
;;
|
|
397
|
+
esac
|
|
398
|
+
done
|
|
399
|
+
req_url="${req_url##*/api/}"
|
|
400
|
+
echo "$req_url"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
# curl wrapper for local/native HTTP endpoints with tab pinning + retry
|
|
404
|
+
local_curl() {
|
|
405
|
+
local sid="$1"; shift
|
|
406
|
+
local tab_id
|
|
407
|
+
tab_id=$(load_tab_id "$sid")
|
|
408
|
+
local max_retries="$LOCAL_SAFE_READ_RETRY_MAX_ATTEMPTS"
|
|
409
|
+
local delay="$LOCAL_SAFE_READ_RETRY_INITIAL_DELAY_S"
|
|
410
|
+
local endpoint retry_profile
|
|
411
|
+
endpoint=$(local_retry_endpoint_from_args "$@")
|
|
412
|
+
retry_profile=$(local_retry_profile "$endpoint")
|
|
413
|
+
for attempt in $(seq 1 $max_retries); do
|
|
414
|
+
RESP=$(curl -s -w "\n%{http_code}" -H "Content-Type: application/json" -H "X-Tab-Id: $tab_id" "$@")
|
|
415
|
+
HTTP_CODE=$(echo "$RESP" | tail -1)
|
|
416
|
+
BODY=$(echo "$RESP" | sed '$d')
|
|
417
|
+
|
|
418
|
+
# Check for TAB_NOT_FOUND — don't retry, clean up session
|
|
419
|
+
CODE=$(echo "$BODY" | jq -r '.code // empty' 2>/dev/null)
|
|
420
|
+
if [ "$CODE" = "TAB_NOT_FOUND" ]; then
|
|
421
|
+
echo "ERROR: Session tab was closed" >&2
|
|
422
|
+
remove_tab_id "$sid"
|
|
423
|
+
exit 1
|
|
424
|
+
fi
|
|
425
|
+
|
|
426
|
+
# Check for retryable conditions (structured field or HTTP status)
|
|
427
|
+
RETRYABLE=$(echo "$BODY" | jq -r '.retryable // false' 2>/dev/null)
|
|
428
|
+
RECOVERY_STATE=$(echo "$BODY" | jq -r '.data.recoveryState // .recoveryState // empty' 2>/dev/null)
|
|
429
|
+
case "$HTTP_CODE" in
|
|
430
|
+
503|504) RETRYABLE=true ;;
|
|
431
|
+
esac
|
|
432
|
+
if [ "$RECOVERY_STATE" = "flapping" ] && [ "$RETRYABLE" = "true" ]; then
|
|
433
|
+
echo "WARN: local bridge is flapping from repeated disconnects; suppressing patient retries for '$endpoint'. Reload the ThinkRun extension or stabilize the page/browser before retrying." >&2
|
|
434
|
+
RETRYABLE=false
|
|
435
|
+
fi
|
|
436
|
+
if [ "$RETRYABLE" = "true" ] && [ "$attempt" -lt "$max_retries" ] && [ "$retry_profile" = "safe_same_bridge" ]; then
|
|
437
|
+
sleep $delay
|
|
438
|
+
delay=$((delay * LOCAL_SAFE_READ_RETRY_BACKOFF_MULTIPLIER))
|
|
439
|
+
continue
|
|
440
|
+
fi
|
|
441
|
+
if [ "$RETRYABLE" = "true" ] && [ "$retry_profile" != "safe_same_bridge" ]; then
|
|
442
|
+
echo "WARN: local command '$endpoint' was not retried after transport failure because the outcome may be unknown; verify page state before retrying." >&2
|
|
443
|
+
fi
|
|
444
|
+
maybe_sync_local_command "$sid" "$HTTP_CODE" "$BODY" "$@"
|
|
445
|
+
echo "$BODY"
|
|
446
|
+
return 0
|
|
447
|
+
done
|
|
448
|
+
maybe_sync_local_command "$sid" "$HTTP_CODE" "$BODY" "$@"
|
|
449
|
+
echo "$BODY"
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
# --- Cloud helpers ---
|
|
453
|
+
|
|
454
|
+
# Retry helper for transient errors (Fly routing, timeouts, 502/503/504/524)
|
|
455
|
+
retry_curl() {
|
|
456
|
+
local max_retries=4
|
|
457
|
+
local delay=1
|
|
458
|
+
for attempt in $(seq 1 $max_retries); do
|
|
459
|
+
RESP=$(curl -s -w "\n%{http_code}" "${H[@]}" "$@")
|
|
460
|
+
HTTP_CODE=$(echo "$RESP" | tail -1)
|
|
461
|
+
BODY=$(echo "$RESP" | sed '$d')
|
|
462
|
+
|
|
463
|
+
# Check for retryable conditions
|
|
464
|
+
SHOULD_RETRY=false
|
|
465
|
+
|
|
466
|
+
# Structured retryable field from bridge/cloud API
|
|
467
|
+
RETRYABLE=$(echo "$BODY" | jq -r '.retryable // false' 2>/dev/null)
|
|
468
|
+
if [ "$RETRYABLE" = "true" ]; then
|
|
469
|
+
SHOULD_RETRY=true
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
# HTTP status codes that indicate transient failures
|
|
473
|
+
case "$HTTP_CODE" in
|
|
474
|
+
502|503|504|524) SHOULD_RETRY=true ;;
|
|
475
|
+
esac
|
|
476
|
+
|
|
477
|
+
# Legacy error string matching
|
|
478
|
+
ERROR=$(echo "$BODY" | jq -r '.error // empty' 2>/dev/null)
|
|
479
|
+
case "$ERROR" in
|
|
480
|
+
"Session not found"|"Session is still provisioning. Please wait for it to become active.")
|
|
481
|
+
SHOULD_RETRY=true ;;
|
|
482
|
+
esac
|
|
483
|
+
|
|
484
|
+
if [ "$SHOULD_RETRY" = "true" ] && [ "$attempt" -lt "$max_retries" ]; then
|
|
485
|
+
sleep $delay
|
|
486
|
+
delay=$((delay * 2))
|
|
487
|
+
continue
|
|
488
|
+
fi
|
|
489
|
+
|
|
490
|
+
echo "$BODY"
|
|
491
|
+
return 0
|
|
492
|
+
done
|
|
493
|
+
echo "$BODY"
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
# Validate local bridge identity once before command dispatch.
|
|
497
|
+
if [ "$MODE" = "local" ]; then
|
|
498
|
+
ensure_local
|
|
499
|
+
fi
|
|
500
|
+
|
|
501
|
+
# --- Commands ---
|
|
502
|
+
case "${1:-help}" in
|
|
503
|
+
|
|
504
|
+
# ========== Session Management ==========
|
|
505
|
+
|
|
506
|
+
session-create)
|
|
507
|
+
if [ "$MODE" = "local" ]; then
|
|
508
|
+
ensure_local
|
|
509
|
+
# Create a new tab via native host
|
|
510
|
+
RESP=$(curl -s -H "Content-Type: application/json" -X POST "$LOCAL_URL/api/tabs/new" \
|
|
511
|
+
-d '{"url":"about:blank"}')
|
|
512
|
+
SUCCESS=$(echo "$RESP" | jq -r '.success // false')
|
|
513
|
+
if [ "$SUCCESS" != "true" ]; then
|
|
514
|
+
echo "ERROR: Failed to create tab (mode: $MODE)" >&2
|
|
515
|
+
echo "$RESP" >&2
|
|
516
|
+
exit 1
|
|
517
|
+
fi
|
|
518
|
+
# Extract tab ID from response
|
|
519
|
+
TAB_ID=$(echo "$RESP" | jq -r '.data.tabId // .data.id // empty')
|
|
520
|
+
if [ -z "$TAB_ID" ]; then
|
|
521
|
+
echo "ERROR: No tab ID in response (mode: $MODE)" >&2
|
|
522
|
+
echo "$RESP" >&2
|
|
523
|
+
exit 1
|
|
524
|
+
fi
|
|
525
|
+
# Prefer backend-issued session IDs for local execution when API auth is available.
|
|
526
|
+
LOCAL_API_KEY=""
|
|
527
|
+
LOCAL_API_URL=""
|
|
528
|
+
if [ -n "${THINKRUN_API_KEY:-}" ]; then
|
|
529
|
+
LOCAL_API_KEY="$THINKRUN_API_KEY"
|
|
530
|
+
elif [ -f "$CONFIG_FILE" ]; then
|
|
531
|
+
LOCAL_API_KEY=$(jq -r '.apiKey // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
532
|
+
fi
|
|
533
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
534
|
+
LOCAL_API_URL=$(jq -r '.apiUrl // empty' "$CONFIG_FILE" 2>/dev/null)
|
|
535
|
+
fi
|
|
536
|
+
LOCAL_BASE="${THINKRUN_URL:-${LOCAL_API_URL:-https://api.thinkbrowse.io}}"
|
|
537
|
+
|
|
538
|
+
SID=""
|
|
539
|
+
if [ -n "$LOCAL_API_KEY" ]; then
|
|
540
|
+
BACKEND_RESP=$(curl -s -H "X-API-Key: $LOCAL_API_KEY" -H "Content-Type: application/json" \
|
|
541
|
+
-X POST "$LOCAL_BASE/api/sessions/local" -d '{}')
|
|
542
|
+
SID=$(echo "$BACKEND_RESP" | jq -r '.data.sessionId // .sessionId // empty')
|
|
543
|
+
fi
|
|
544
|
+
if [ -z "$SID" ]; then
|
|
545
|
+
SID="local-$TAB_ID"
|
|
546
|
+
fi
|
|
547
|
+
save_tab_id "$SID" "$TAB_ID"
|
|
548
|
+
echo "$SID"
|
|
549
|
+
else
|
|
550
|
+
RESP=$(curl -s "${H[@]}" -X POST "$BASE/api/sessions" -d '{}')
|
|
551
|
+
SID=$(echo "$RESP" | jq -r '.data.sessionId // .sessionId // empty')
|
|
552
|
+
if [ -z "$SID" ]; then
|
|
553
|
+
echo "ERROR: Session creation failed" >&2
|
|
554
|
+
echo "$RESP" >&2
|
|
555
|
+
exit 1
|
|
556
|
+
fi
|
|
557
|
+
# Poll until ready (max 120s for cold machine start)
|
|
558
|
+
for i in $(seq 1 40); do
|
|
559
|
+
STATUS=$(curl -s "${H[@]}" "$BASE/api/sessions/$SID" | jq -r '.data.status // .status // "unknown"')
|
|
560
|
+
case "$STATUS" in
|
|
561
|
+
ready|active|running)
|
|
562
|
+
echo "$SID"
|
|
563
|
+
exit 0
|
|
564
|
+
;;
|
|
565
|
+
creating|provisioning|pending)
|
|
566
|
+
printf "." >&2
|
|
567
|
+
sleep 3
|
|
568
|
+
;;
|
|
569
|
+
failed|error|terminated)
|
|
570
|
+
echo "" >&2
|
|
571
|
+
echo "ERROR: Session $SID failed with status: $STATUS" >&2
|
|
572
|
+
exit 1
|
|
573
|
+
;;
|
|
574
|
+
esac
|
|
575
|
+
done
|
|
576
|
+
echo "" >&2
|
|
577
|
+
echo "ERROR: Session $SID not ready after 120s" >&2
|
|
578
|
+
exit 1
|
|
579
|
+
fi
|
|
580
|
+
;;
|
|
581
|
+
|
|
582
|
+
session-status)
|
|
583
|
+
if [ "$MODE" = "local" ]; then
|
|
584
|
+
ensure_local
|
|
585
|
+
TAB_ID=$(load_tab_id "$2")
|
|
586
|
+
curl -s -H "Content-Type: application/json" -H "X-Tab-Id: $TAB_ID" "$LOCAL_URL/sessions/$TAB_ID"
|
|
587
|
+
else
|
|
588
|
+
curl -s "${H[@]}" "$BASE/api/sessions/$2"
|
|
589
|
+
fi
|
|
590
|
+
;;
|
|
591
|
+
|
|
592
|
+
session-delete)
|
|
593
|
+
if [ "$MODE" = "local" ]; then
|
|
594
|
+
TAB_ID=$(load_tab_id "$2")
|
|
595
|
+
curl -s -H "Content-Type: application/json" -X POST "$LOCAL_URL/api/tabs/close" \
|
|
596
|
+
-d "{\"tabId\":$TAB_ID}"
|
|
597
|
+
remove_tab_id "$2"
|
|
598
|
+
else
|
|
599
|
+
curl -s "${H[@]}" -X DELETE "$BASE/api/sessions/$2"
|
|
600
|
+
fi
|
|
601
|
+
;;
|
|
602
|
+
|
|
603
|
+
session-details)
|
|
604
|
+
if [ "$MODE" = "local" ]; then
|
|
605
|
+
if can_sync_local_session "$2"; then
|
|
606
|
+
BACKEND_DETAILS=$(curl -s -H "X-API-Key: $LOCAL_SYNC_API_KEY" -H "Content-Type: application/json" \
|
|
607
|
+
-X GET "$LOCAL_SYNC_BASE/api/sessions/$2/details" 2>/dev/null || true)
|
|
608
|
+
if [ -n "$BACKEND_DETAILS" ] && [ "$(echo "$BACKEND_DETAILS" | jq -r '.success // false' 2>/dev/null)" = "true" ]; then
|
|
609
|
+
echo "$BACKEND_DETAILS"
|
|
610
|
+
exit 0
|
|
611
|
+
fi
|
|
612
|
+
fi
|
|
613
|
+
TAB_ID=$(load_tab_id "$2")
|
|
614
|
+
SESSION_JSON=$(curl -s -H "Content-Type: application/json" -H "X-Tab-Id: $TAB_ID" "$LOCAL_URL/sessions/$TAB_ID")
|
|
615
|
+
URL_VAL=$(echo "$SESSION_JSON" | jq -r '.data.url // ""')
|
|
616
|
+
TITLE_VAL=$(echo "$SESSION_JSON" | jq -r '.data.title // ""')
|
|
617
|
+
jq -n \
|
|
618
|
+
--arg sid "$2" \
|
|
619
|
+
--arg tabId "$TAB_ID" \
|
|
620
|
+
--arg url "$URL_VAL" \
|
|
621
|
+
--arg title "$TITLE_VAL" \
|
|
622
|
+
'{
|
|
623
|
+
success: true,
|
|
624
|
+
session: {
|
|
625
|
+
sessionId: $sid,
|
|
626
|
+
executionMode: "local",
|
|
627
|
+
executor: "native-host",
|
|
628
|
+
status: "active",
|
|
629
|
+
tabId: ($tabId|tonumber),
|
|
630
|
+
currentUrl: $url,
|
|
631
|
+
title: $title,
|
|
632
|
+
artifacts: [],
|
|
633
|
+
actions: [],
|
|
634
|
+
storageBacked: false
|
|
635
|
+
}
|
|
636
|
+
}'
|
|
637
|
+
else
|
|
638
|
+
retry_curl -X GET "$BASE/api/sessions/$2/details"
|
|
639
|
+
fi
|
|
640
|
+
;;
|
|
641
|
+
|
|
642
|
+
session-actions)
|
|
643
|
+
if [ "$MODE" = "local" ]; then
|
|
644
|
+
if can_sync_local_session "$2"; then
|
|
645
|
+
LIMIT="${3:-50}"
|
|
646
|
+
OFFSET="${4:-0}"
|
|
647
|
+
TYPE="${5:-}"
|
|
648
|
+
ACTIONS_URL="$LOCAL_SYNC_BASE/api/sessions/$2/actions?limit=$LIMIT&offset=$OFFSET"
|
|
649
|
+
if [ -n "$TYPE" ]; then
|
|
650
|
+
ACTIONS_URL="${ACTIONS_URL}&type=$TYPE"
|
|
651
|
+
fi
|
|
652
|
+
BACKEND_ACTIONS=$(curl -s -H "X-API-Key: $LOCAL_SYNC_API_KEY" -H "Content-Type: application/json" \
|
|
653
|
+
-X GET "$ACTIONS_URL" 2>/dev/null || true)
|
|
654
|
+
if [ -n "$BACKEND_ACTIONS" ] && [ "$(echo "$BACKEND_ACTIONS" | jq -r '.success // false' 2>/dev/null)" = "true" ]; then
|
|
655
|
+
echo "$BACKEND_ACTIONS"
|
|
656
|
+
exit 0
|
|
657
|
+
fi
|
|
658
|
+
fi
|
|
659
|
+
TAB_ID=$(load_tab_id "$2")
|
|
660
|
+
CONSOLE_JSON=$(local_curl "$2" -X POST "$LOCAL_URL/api/console")
|
|
661
|
+
NETWORK_JSON=$(local_curl "$2" -X POST "$LOCAL_URL/api/network")
|
|
662
|
+
LIMIT="${3:-50}"
|
|
663
|
+
OFFSET="${4:-0}"
|
|
664
|
+
jq -n \
|
|
665
|
+
--arg sid "$2" \
|
|
666
|
+
--argjson limit "$LIMIT" \
|
|
667
|
+
--argjson offset "$OFFSET" \
|
|
668
|
+
--argjson console "$CONSOLE_JSON" \
|
|
669
|
+
--argjson network "$NETWORK_JSON" \
|
|
670
|
+
'{
|
|
671
|
+
success: true,
|
|
672
|
+
data: {
|
|
673
|
+
sessionId: $sid,
|
|
674
|
+
executionMode: "local",
|
|
675
|
+
actions: [],
|
|
676
|
+
console: ($console.data // []),
|
|
677
|
+
network: ($network.data // []),
|
|
678
|
+
total: 0,
|
|
679
|
+
successful: 0,
|
|
680
|
+
failed: 0,
|
|
681
|
+
hasMore: false,
|
|
682
|
+
note: "Local mode currently exposes console and network logs; action history persistence is cloud-only."
|
|
683
|
+
}
|
|
684
|
+
}'
|
|
685
|
+
else
|
|
686
|
+
LIMIT="${3:-50}"
|
|
687
|
+
OFFSET="${4:-0}"
|
|
688
|
+
TYPE="${5:-}"
|
|
689
|
+
ACTIONS_URL="$BASE/api/sessions/$2/actions?limit=$LIMIT&offset=$OFFSET"
|
|
690
|
+
if [ -n "$TYPE" ]; then
|
|
691
|
+
ACTIONS_URL="${ACTIONS_URL}&type=$TYPE"
|
|
692
|
+
fi
|
|
693
|
+
retry_curl -X GET "$ACTIONS_URL"
|
|
694
|
+
fi
|
|
695
|
+
;;
|
|
696
|
+
|
|
697
|
+
session-artifacts)
|
|
698
|
+
if [ "$MODE" = "local" ]; then
|
|
699
|
+
if can_sync_local_session "$2"; then
|
|
700
|
+
BACKEND_ARTIFACTS=$(curl -s -H "X-API-Key: $LOCAL_SYNC_API_KEY" -H "Content-Type: application/json" \
|
|
701
|
+
-X GET "$LOCAL_SYNC_BASE/api/sessions/$2/artifacts" 2>/dev/null || true)
|
|
702
|
+
if [ -n "$BACKEND_ARTIFACTS" ] && [ "$(echo "$BACKEND_ARTIFACTS" | jq -r '.success // false' 2>/dev/null)" = "true" ]; then
|
|
703
|
+
echo "$BACKEND_ARTIFACTS"
|
|
704
|
+
exit 0
|
|
705
|
+
fi
|
|
706
|
+
fi
|
|
707
|
+
jq -n --arg sid "$2" '{
|
|
708
|
+
success: true,
|
|
709
|
+
sessionId: $sid,
|
|
710
|
+
executionMode: "local",
|
|
711
|
+
artifacts: [],
|
|
712
|
+
note: "Local mode does not persist artifacts to cloud storage. Use --mode cloud for reusable stored artifacts."
|
|
713
|
+
}'
|
|
714
|
+
else
|
|
715
|
+
retry_curl -X GET "$BASE/api/sessions/$2/artifacts"
|
|
716
|
+
fi
|
|
717
|
+
;;
|
|
718
|
+
|
|
719
|
+
artifact-download)
|
|
720
|
+
if [ "$MODE" = "local" ]; then
|
|
721
|
+
echo "ERROR: artifact-download is available in cloud mode only (use --mode cloud)" >&2
|
|
722
|
+
exit 1
|
|
723
|
+
fi
|
|
724
|
+
ARTIFACT_ID="$2"
|
|
725
|
+
OUTFILE="${3:-artifact-$ARTIFACT_ID.bin}"
|
|
726
|
+
if ! curl -fsSL -H "X-API-Key: $API_KEY" "$BASE/api/storage/artifacts/$ARTIFACT_ID/download" -o "$OUTFILE"; then
|
|
727
|
+
echo "ERROR: Failed to download artifact $ARTIFACT_ID" >&2
|
|
728
|
+
exit 1
|
|
729
|
+
fi
|
|
730
|
+
echo "$OUTFILE"
|
|
731
|
+
;;
|
|
732
|
+
|
|
733
|
+
# ========== Navigation ==========
|
|
734
|
+
|
|
735
|
+
goto)
|
|
736
|
+
if [ "$MODE" = "local" ]; then
|
|
737
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/navigate" \
|
|
738
|
+
-d "{\"url\":$(printf '%s' "$3" | jq -Rs)}"
|
|
739
|
+
else
|
|
740
|
+
retry_curl -X POST "$BASE/api/sessions/$2/navigate" \
|
|
741
|
+
-d "{\"url\":$(printf '%s' "$3" | jq -Rs)}"
|
|
742
|
+
fi
|
|
743
|
+
;;
|
|
744
|
+
|
|
745
|
+
scroll)
|
|
746
|
+
DIR="${3:-down}"
|
|
747
|
+
AMT="${4:-500}"
|
|
748
|
+
if ! [[ "$AMT" =~ ^[0-9]+$ ]]; then
|
|
749
|
+
echo "ERROR: Amount must be a positive integer" >&2
|
|
750
|
+
exit 1
|
|
751
|
+
fi
|
|
752
|
+
if [ "$MODE" = "local" ]; then
|
|
753
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/scroll" \
|
|
754
|
+
-d "{\"direction\":\"$DIR\",\"amount\":$AMT}"
|
|
755
|
+
else
|
|
756
|
+
if [ "$DIR" = "up" ]; then
|
|
757
|
+
retry_curl -X POST "$BASE/api/sessions/$2/evaluate" \
|
|
758
|
+
-d "{\"script\":\"window.scrollBy(0, -$AMT)\"}"
|
|
759
|
+
else
|
|
760
|
+
retry_curl -X POST "$BASE/api/sessions/$2/evaluate" \
|
|
761
|
+
-d "{\"script\":\"window.scrollBy(0, $AMT)\"}"
|
|
762
|
+
fi
|
|
763
|
+
fi
|
|
764
|
+
;;
|
|
765
|
+
|
|
766
|
+
# ========== Observation ==========
|
|
767
|
+
|
|
768
|
+
snapshot)
|
|
769
|
+
if [ "$MODE" = "local" ]; then
|
|
770
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/snapshot" \
|
|
771
|
+
| jq -r '.data // .error // "No snapshot available"'
|
|
772
|
+
else
|
|
773
|
+
retry_curl -X POST "$BASE/api/sessions/$2/snapshot" \
|
|
774
|
+
| jq -r '.snapshot // .error // "No snapshot available"'
|
|
775
|
+
fi
|
|
776
|
+
;;
|
|
777
|
+
|
|
778
|
+
screenshot)
|
|
779
|
+
if [ "$MODE" = "local" ]; then
|
|
780
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/screenshot"
|
|
781
|
+
else
|
|
782
|
+
retry_curl -X POST "$BASE/api/sessions/$2/screenshot"
|
|
783
|
+
fi
|
|
784
|
+
;;
|
|
785
|
+
|
|
786
|
+
extract)
|
|
787
|
+
TYPE="${3:-text}"
|
|
788
|
+
if [ "$MODE" = "local" ]; then
|
|
789
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/extract" \
|
|
790
|
+
-d "{\"type\":$(printf '%s' "$TYPE" | jq -Rs)}"
|
|
791
|
+
else
|
|
792
|
+
retry_curl -X POST "$BASE/api/sessions/$2/extract" \
|
|
793
|
+
-d "{\"type\":$(printf '%s' "$TYPE" | jq -Rs)}"
|
|
794
|
+
fi
|
|
795
|
+
;;
|
|
796
|
+
|
|
797
|
+
# ========== Interaction ==========
|
|
798
|
+
|
|
799
|
+
click)
|
|
800
|
+
if [ "$MODE" = "local" ]; then
|
|
801
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/click" \
|
|
802
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs)}"
|
|
803
|
+
else
|
|
804
|
+
retry_curl -X POST "$BASE/api/sessions/$2/click" \
|
|
805
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs)}"
|
|
806
|
+
fi
|
|
807
|
+
;;
|
|
808
|
+
|
|
809
|
+
fill)
|
|
810
|
+
if [ "$MODE" = "local" ]; then
|
|
811
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/fill" \
|
|
812
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"value\":$(printf '%s' "$4" | jq -Rs)}"
|
|
813
|
+
else
|
|
814
|
+
retry_curl -X POST "$BASE/api/sessions/$2/fill" \
|
|
815
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"value\":$(printf '%s' "$4" | jq -Rs)}"
|
|
816
|
+
fi
|
|
817
|
+
;;
|
|
818
|
+
|
|
819
|
+
type)
|
|
820
|
+
if [ "$MODE" = "local" ]; then
|
|
821
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/type" \
|
|
822
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"text\":$(printf '%s' "$4" | jq -Rs)}"
|
|
823
|
+
else
|
|
824
|
+
retry_curl -X POST "$BASE/api/sessions/$2/type" \
|
|
825
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"text\":$(printf '%s' "$4" | jq -Rs)}"
|
|
826
|
+
fi
|
|
827
|
+
;;
|
|
828
|
+
|
|
829
|
+
press)
|
|
830
|
+
if [ "$MODE" = "local" ]; then
|
|
831
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/press" \
|
|
832
|
+
-d "{\"key\":$(printf '%s' "$3" | jq -Rs)}"
|
|
833
|
+
else
|
|
834
|
+
retry_curl -X POST "$BASE/api/sessions/$2/press" \
|
|
835
|
+
-d "{\"key\":$(printf '%s' "$3" | jq -Rs)}"
|
|
836
|
+
fi
|
|
837
|
+
;;
|
|
838
|
+
|
|
839
|
+
hover)
|
|
840
|
+
if [ "$MODE" = "local" ]; then
|
|
841
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/hover" \
|
|
842
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs)}"
|
|
843
|
+
else
|
|
844
|
+
retry_curl -X POST "$BASE/api/sessions/$2/hover" \
|
|
845
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs)}"
|
|
846
|
+
fi
|
|
847
|
+
;;
|
|
848
|
+
|
|
849
|
+
select)
|
|
850
|
+
if [ "$MODE" = "local" ]; then
|
|
851
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/select" \
|
|
852
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"value\":$(printf '%s' "$4" | jq -Rs)}"
|
|
853
|
+
else
|
|
854
|
+
retry_curl -X POST "$BASE/api/sessions/$2/select" \
|
|
855
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"value\":$(printf '%s' "$4" | jq -Rs)}"
|
|
856
|
+
fi
|
|
857
|
+
;;
|
|
858
|
+
|
|
859
|
+
wait)
|
|
860
|
+
TIMEOUT="${4:-10000}"
|
|
861
|
+
if ! [[ "$TIMEOUT" =~ ^[0-9]+$ ]]; then
|
|
862
|
+
echo "ERROR: Timeout must be a positive integer (ms)" >&2
|
|
863
|
+
exit 1
|
|
864
|
+
fi
|
|
865
|
+
if [ "$MODE" = "local" ]; then
|
|
866
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/wait" \
|
|
867
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"timeout\":$TIMEOUT}"
|
|
868
|
+
else
|
|
869
|
+
retry_curl -X POST "$BASE/api/sessions/$2/wait" \
|
|
870
|
+
-d "{\"selector\":$(printf '%s' "$3" | jq -Rs),\"timeout\":$TIMEOUT}"
|
|
871
|
+
fi
|
|
872
|
+
;;
|
|
873
|
+
|
|
874
|
+
evaluate)
|
|
875
|
+
if [ "$MODE" = "local" ]; then
|
|
876
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/evaluate" \
|
|
877
|
+
-d "{\"script\":$(printf '%s' "$3" | jq -Rs)}"
|
|
878
|
+
else
|
|
879
|
+
retry_curl -X POST "$BASE/api/sessions/$2/evaluate" \
|
|
880
|
+
-d "{\"script\":$(printf '%s' "$3" | jq -Rs)}"
|
|
881
|
+
fi
|
|
882
|
+
;;
|
|
883
|
+
|
|
884
|
+
# ========== Navigation History ==========
|
|
885
|
+
|
|
886
|
+
back)
|
|
887
|
+
if [ "$MODE" = "local" ]; then
|
|
888
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/go-back"
|
|
889
|
+
else
|
|
890
|
+
retry_curl -X POST "$BASE/api/sessions/$2/go-back"
|
|
891
|
+
fi
|
|
892
|
+
;;
|
|
893
|
+
|
|
894
|
+
forward)
|
|
895
|
+
if [ "$MODE" = "local" ]; then
|
|
896
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/go-forward"
|
|
897
|
+
else
|
|
898
|
+
retry_curl -X POST "$BASE/api/sessions/$2/go-forward"
|
|
899
|
+
fi
|
|
900
|
+
;;
|
|
901
|
+
|
|
902
|
+
# ========== Dialog Handling ==========
|
|
903
|
+
|
|
904
|
+
dialog)
|
|
905
|
+
local dialog_action="${3:-status}"
|
|
906
|
+
case "$dialog_action" in
|
|
907
|
+
status)
|
|
908
|
+
if [ "$MODE" = "local" ]; then
|
|
909
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/dialog"
|
|
910
|
+
else
|
|
911
|
+
retry_curl -X GET "$BASE/api/sessions/$2/dialog"
|
|
912
|
+
fi
|
|
913
|
+
;;
|
|
914
|
+
accept)
|
|
915
|
+
if [ "$MODE" = "local" ]; then
|
|
916
|
+
echo '{"info":"Dialogs are auto-handled in local mode"}'
|
|
917
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/dialog"
|
|
918
|
+
else
|
|
919
|
+
retry_curl -X POST "$BASE/api/sessions/$2/dialog" \
|
|
920
|
+
-d "{\"accept\":true$([ -n "${4:-}" ] && echo ",\"promptText\":$(printf '%s' "$4" | jq -Rs)" || echo "")}"
|
|
921
|
+
fi
|
|
922
|
+
;;
|
|
923
|
+
dismiss)
|
|
924
|
+
if [ "$MODE" = "local" ]; then
|
|
925
|
+
echo '{"info":"Dialogs are auto-handled in local mode"}'
|
|
926
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/dialog"
|
|
927
|
+
else
|
|
928
|
+
retry_curl -X POST "$BASE/api/sessions/$2/dialog" \
|
|
929
|
+
-d '{"accept":false}'
|
|
930
|
+
fi
|
|
931
|
+
;;
|
|
932
|
+
*)
|
|
933
|
+
echo "ERROR: Unknown dialog action: $dialog_action (use status, accept, dismiss)" >&2
|
|
934
|
+
exit 1
|
|
935
|
+
;;
|
|
936
|
+
esac
|
|
937
|
+
;;
|
|
938
|
+
|
|
939
|
+
# ========== Wait for Text ==========
|
|
940
|
+
|
|
941
|
+
wait-for-text)
|
|
942
|
+
local text_val="$3"
|
|
943
|
+
local wft_timeout="${4:-30000}"
|
|
944
|
+
if [ "$MODE" = "local" ]; then
|
|
945
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/wait-for-text" \
|
|
946
|
+
-d "{\"text\":$(printf '%s' "$text_val" | jq -Rs),\"timeout\":$wft_timeout}"
|
|
947
|
+
else
|
|
948
|
+
retry_curl -X POST "$BASE/api/sessions/$2/wait-for-text" \
|
|
949
|
+
-d "{\"text\":$(printf '%s' "$text_val" | jq -Rs),\"timeout\":$wft_timeout}"
|
|
950
|
+
fi
|
|
951
|
+
;;
|
|
952
|
+
|
|
953
|
+
# ========== Observability ==========
|
|
954
|
+
|
|
955
|
+
console)
|
|
956
|
+
if [ "$MODE" = "local" ]; then
|
|
957
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/console"
|
|
958
|
+
else
|
|
959
|
+
retry_curl -X GET "$BASE/api/sessions/$2/console"
|
|
960
|
+
fi
|
|
961
|
+
;;
|
|
962
|
+
|
|
963
|
+
network)
|
|
964
|
+
if [ "$MODE" = "local" ]; then
|
|
965
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/network"
|
|
966
|
+
else
|
|
967
|
+
retry_curl -X GET "$BASE/api/sessions/$2/network"
|
|
968
|
+
fi
|
|
969
|
+
;;
|
|
970
|
+
|
|
971
|
+
url)
|
|
972
|
+
if [ "$MODE" = "local" ]; then
|
|
973
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/url"
|
|
974
|
+
else
|
|
975
|
+
# Cloud: extract URL from snapshot
|
|
976
|
+
retry_curl -X POST "$BASE/api/sessions/$2/evaluate" \
|
|
977
|
+
-d '{"script":"window.location.href"}'
|
|
978
|
+
fi
|
|
979
|
+
;;
|
|
980
|
+
|
|
981
|
+
title)
|
|
982
|
+
if [ "$MODE" = "local" ]; then
|
|
983
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/title"
|
|
984
|
+
else
|
|
985
|
+
# Cloud: extract title from evaluate
|
|
986
|
+
retry_curl -X POST "$BASE/api/sessions/$2/evaluate" \
|
|
987
|
+
-d '{"script":"document.title"}'
|
|
988
|
+
fi
|
|
989
|
+
;;
|
|
990
|
+
|
|
991
|
+
html)
|
|
992
|
+
if [ "$MODE" = "local" ]; then
|
|
993
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/html"
|
|
994
|
+
else
|
|
995
|
+
retry_curl -X POST "$BASE/api/sessions/$2/extract" \
|
|
996
|
+
-d '{"type":"html"}'
|
|
997
|
+
fi
|
|
998
|
+
;;
|
|
999
|
+
|
|
1000
|
+
clear-logs)
|
|
1001
|
+
if [ "$MODE" = "local" ]; then
|
|
1002
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/clear-logs"
|
|
1003
|
+
else
|
|
1004
|
+
echo "ERROR: clear-logs only available in local mode" >&2
|
|
1005
|
+
exit 1
|
|
1006
|
+
fi
|
|
1007
|
+
;;
|
|
1008
|
+
|
|
1009
|
+
# ========== Tab Management (local only) ==========
|
|
1010
|
+
|
|
1011
|
+
tabs)
|
|
1012
|
+
if [ "$MODE" = "local" ]; then
|
|
1013
|
+
curl -s -H "Content-Type: application/json" "$LOCAL_URL/api/tabs"
|
|
1014
|
+
else
|
|
1015
|
+
echo "ERROR: Tab management only available in local mode" >&2
|
|
1016
|
+
exit 1
|
|
1017
|
+
fi
|
|
1018
|
+
;;
|
|
1019
|
+
|
|
1020
|
+
switch-tab)
|
|
1021
|
+
if [ "$MODE" = "local" ]; then
|
|
1022
|
+
local_curl "$2" -X POST "$LOCAL_URL/api/tabs/switch" \
|
|
1023
|
+
-d "{\"tabId\":$3}"
|
|
1024
|
+
else
|
|
1025
|
+
echo "ERROR: Tab management only available in local mode" >&2
|
|
1026
|
+
exit 1
|
|
1027
|
+
fi
|
|
1028
|
+
;;
|
|
1029
|
+
|
|
1030
|
+
# ========== Help ==========
|
|
1031
|
+
|
|
1032
|
+
help|*)
|
|
1033
|
+
echo "browse — Portable browser automation for AI agents"
|
|
1034
|
+
echo ""
|
|
1035
|
+
echo "Mode: ${MODE:-not detected} (local=native host $LOCAL_HOST:$LOCAL_PORT, cloud=Playwright)"
|
|
1036
|
+
echo ""
|
|
1037
|
+
echo "Session Management:"
|
|
1038
|
+
echo " session-create Create session, print ID"
|
|
1039
|
+
echo " session-status <sid> Get session details"
|
|
1040
|
+
echo " session-delete <sid> Close session"
|
|
1041
|
+
echo " session-details <sid> Get session details (cloud/local)"
|
|
1042
|
+
echo " session-actions <sid> [limit] [off] Get session actions/logs (cloud/local)"
|
|
1043
|
+
echo " session-artifacts <sid> List session artifacts (cloud/local)"
|
|
1044
|
+
echo " artifact-download <id> [out] Download cloud artifact by ID"
|
|
1045
|
+
echo ""
|
|
1046
|
+
echo "Navigation:"
|
|
1047
|
+
echo " goto <sid> <url> Navigate to URL"
|
|
1048
|
+
echo " back <sid> Go back in browser history"
|
|
1049
|
+
echo " forward <sid> Go forward in browser history"
|
|
1050
|
+
echo " scroll <sid> [up|down] [pixels] Scroll page (default: down 500)"
|
|
1051
|
+
echo ""
|
|
1052
|
+
echo "Observation:"
|
|
1053
|
+
echo " snapshot <sid> Get DOM tree / accessibility snapshot"
|
|
1054
|
+
echo " screenshot <sid> Take screenshot"
|
|
1055
|
+
echo " extract <sid> [text|html] Extract page content"
|
|
1056
|
+
echo " console <sid> Get browser console logs"
|
|
1057
|
+
echo " network <sid> Get network requests"
|
|
1058
|
+
echo " url <sid> Get current page URL"
|
|
1059
|
+
echo " title <sid> Get current page title"
|
|
1060
|
+
echo " html <sid> Get full page HTML"
|
|
1061
|
+
echo ""
|
|
1062
|
+
echo "Interaction:"
|
|
1063
|
+
echo " click <sid> <selector> Click element"
|
|
1064
|
+
echo " fill <sid> <selector> <value> Fill form field (clears first)"
|
|
1065
|
+
echo " type <sid> <selector> <text> Type text (appends)"
|
|
1066
|
+
echo " press <sid> <key> Press key (Enter, Tab, Escape)"
|
|
1067
|
+
echo " hover <sid> <selector> Hover over element"
|
|
1068
|
+
echo " select <sid> <selector> <value> Select dropdown option"
|
|
1069
|
+
echo " wait <sid> <selector> [timeout_ms] Wait for element (default 10000ms)"
|
|
1070
|
+
echo " wait-for-text <sid> <text> [timeout] Wait for text to appear (default 30000ms)"
|
|
1071
|
+
echo " dialog <sid> [status|accept|dismiss] Handle browser dialog (alert/confirm/prompt)"
|
|
1072
|
+
echo " evaluate <sid> <javascript> Run JavaScript on page"
|
|
1073
|
+
echo ""
|
|
1074
|
+
echo "Local Only:"
|
|
1075
|
+
echo " clear-logs <sid> Clear console/network logs"
|
|
1076
|
+
echo " tabs List all open tabs"
|
|
1077
|
+
echo " switch-tab <sid> <tabId> Switch to specific tab"
|
|
1078
|
+
echo ""
|
|
1079
|
+
echo "Tab Listing Examples:"
|
|
1080
|
+
echo " browse tabs | jq -r '.data.tabs[] | \"\\(.id)\\t\\(.active)\\t\\(.url)\"'"
|
|
1081
|
+
echo " browse tabs | jq -r '.data.tabs[] | \"\\(.id)\\t\\(.active)\\t\\(.title)\\t\\(.url)\"'"
|
|
1082
|
+
echo ""
|
|
1083
|
+
echo "Flags:"
|
|
1084
|
+
echo " --mode local|cloud Override mode detection"
|
|
1085
|
+
echo ""
|
|
1086
|
+
echo "Port Discovery:"
|
|
1087
|
+
echo " The native host writes its port to ~/.thinkbrowse/port on startup."
|
|
1088
|
+
echo " This script reads it automatically. Override: THINKRUN_LOCAL_PORT env var."
|
|
1089
|
+
echo ""
|
|
1090
|
+
echo "Environment:"
|
|
1091
|
+
echo " THINKRUN_LOCAL=true Force local mode"
|
|
1092
|
+
echo " THINKRUN_LOCAL_PORT=<port> Override native host port"
|
|
1093
|
+
echo " THINKRUN_BRIDGE_PORT=<port> Legacy override alias (deprecated)"
|
|
1094
|
+
echo " THINKRUN_API_KEY=... Cloud API key"
|
|
1095
|
+
echo " THINKRUN_URL=... Cloud API base URL"
|
|
1096
|
+
echo " ~/.config/thinkrun/config.json Cloud config file"
|
|
1097
|
+
echo ""
|
|
1098
|
+
echo "Mode detection order:"
|
|
1099
|
+
echo " 1. --mode flag or THINKRUN_LOCAL=true"
|
|
1100
|
+
echo " 2. Native host health check ($LOCAL_HOST:$LOCAL_PORT)"
|
|
1101
|
+
echo " 3. THINKRUN_API_KEY or config file → cloud"
|
|
1102
|
+
echo ""
|
|
1103
|
+
echo "Docs:"
|
|
1104
|
+
echo " https://thinkbrowse.io/docs"
|
|
1105
|
+
echo " https://thinkbrowse.io/llms.txt"
|
|
1106
|
+
;;
|
|
1107
|
+
esac
|