kinopoisk-test/scripts/sam-cli.sh
2026-08-13 18:48:56 +03:00

223 lines
8.1 KiB
Bash
Executable File

#!/bin/sh
#
# sam-cli — agent-friendly CLI for the webOS SAM daemon
# (System Application Manager, Luna service com.webos.applicationManager).
#
# Thin wrapper around luna-send with NO hard argument schemas: unknown/extra
# args never crash it, JSON is passed through verbatim, and it works both
# directly on the TV board and remotely over SSH.
#
# Pure POSIX sh (busybox ash compatible) — runs on the webOS TV itself, which
# has no real bash. jq is used when present, otherwise built-in fallbacks.
#
# Sources: .pi/skills/sam-usage/SKILL.md
#
# Usage:
# sam-cli [GLOBAL OPTS] <command> [args...]
#
# Global options (before the command):
# --host <target> SSH target (user@host) to run luna-send on.
# Defaults to $SAM_HOST. If unset, luna-send runs locally.
# --ssh-args <str> Extra ssh options as a whitespace-separated string,
# e.g. "--ssh-args '-p 2222 -i ~/.ssh/id_ed25519'".
# --dev Use the dev-only /dev category (needs devmode enabled).
# --pretty Pretty-print JSON responses (needs jq).
# --timeout <secs> Cap total execution time, 0 disables (default 30).
# --wait <secs> Sleep after a successful launch/close — helps on slow
# SSH links where the reply arrives with delay.
# -h|--help Show this help.
#
# Commands (URI => luna://com.webos.applicationManager[/dev]/<method>):
# launch <id> [extra-json] Launch app by id / launchPointId / instanceId.
# extra-json (optional) is merged into the
# payload, e.g. '{"params":{"key":"value"}}'.
# close <id> Stop an app by id or instanceId (method: close;
# same as closeByAppId).
# running List running apps (JSON under "running").
# list | list-apps | ls List installed apps (JSON under "apps").
# status <appId> getAppStatus for one app.
# info <appId> getAppInfo for one app.
# launch-points listLaunchPoints (JSON).
# manager-info managerInfo (only valid with --dev).
#
# Agent conveniences:
# --ids After `running`/`list`, print only app ids,
# one per line (jq or grep fallback).
#
# Examples:
# sam-cli launch com.webos.app.enactbrowser
# sam-cli launch com.webos.app.enactbrowser '{"params":{"url":"https://x"}}'
# sam-cli close com.webos.app.enactbrowser
# sam-cli running --ids
# sam-cli --host root@tv-ip list --pretty | jq '.apps[].id'
#
set -eu
SERVICE="com.webos.applicationManager"
LUNASEND="${LUNASEND:-luna-send}"
SSH_TARGET="${SAM_HOST:-}"
SSH_ARGS=""
DEV=0
PRETTY=0
TIMEOUT=30
WAIT=0
usage() {
sed -n '2,70p' "$0" | sed 's/^# \{0,1\}//'
}
die() {
printf 'sam-cli: %s\n' "$*" >&2
exit 1
}
# --- global option parsing: consumes options until the first positional ----
while [ "$#" -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
--host) [ "$#" -ge 2 ] || die "--host needs a value"
SSH_TARGET="$2"; shift 2 ;;
--host=*) SSH_TARGET="${1#*=}"; shift ;;
--ssh-args) [ "$#" -ge 2 ] || die "--ssh-args needs a value"
SSH_ARGS="$SSH_ARGS $2"; shift 2 ;;
--dev) DEV=1; shift ;;
--pretty) PRETTY=1; shift ;;
--timeout) [ "$#" -ge 2 ] || die "--timeout needs a value"
TIMEOUT="$2"; shift 2 ;;
--wait) [ "$#" -ge 2 ] || die "--wait needs a value"
WAIT="$2"; shift 2 ;;
--) shift; break ;;
-*) die "unknown option: $1 (run 'sam-cli --help')" ;;
*) break ;;
esac
done
CMD="${1:-}"
[ -n "$CMD" ] || { usage; exit 2; }
shift
# --- payload helpers --------------------------------------------------------
json_base() { # $1 = field name, $2 = value -> {"<field>":"<value>"}
printf '{"%s":"%s"}' "$1" "$2"
}
# Merge extra JSON into a base payload, output COMPACT (single line).
# Uses jq when available; POSIX fallback strips outer braces of the extra
# object and splices the inner content into the base object.
merge_json() {
base="$1" extra="$2" inner="" merged=""
if command -v jq >/dev/null 2>&1; then
# jq absent OR fails (bad json, stub) -> fall through to sed splice
merged=$(jq -c -n --argjson base "$base" --argjson extra "$extra" '$base + $extra' 2>/dev/null) || merged=""
fi
if [ -n "$merged" ]; then
printf '%s' "$merged"
return
fi
inner=$(printf '%s' "$extra" | sed 's/^ *{//; s/} *$//')
if [ -n "$inner" ]; then
printf '%s,%s}' "${base%\}}" "$inner"
else
printf '%s' "$base"
fi
}
print_out() { # payload -> stdout (pretty if requested and possible)
if [ "$PRETTY" -eq 1 ] && command -v jq >/dev/null 2>&1; then
printf '%s\n' "$1" | jq .
else
printf '%s\n' "$1"
fi
}
# --- execution --------------------------------------------------------------
run_luna() { # uri payload
uri="$1" payload="$2" out="" rc=0
base_uri="luna://$SERVICE"
[ "$DEV" -eq 1 ] && base_uri="$base_uri/dev"
uri="$base_uri/$uri"
if [ -n "$SSH_TARGET" ]; then
# payload is piped over stdin and read back remotely via "$(cat)",
# so any quotes/newlines survive without fragile shell escaping.
if [ "$TIMEOUT" -gt 0 ]; then
out=$(printf '%s\n' "$payload" | timeout "$TIMEOUT" ssh \
-o BatchMode=yes -o ConnectTimeout=10 $SSH_ARGS "$SSH_TARGET" \
"luna-send -n 1 '$uri' \"\$(cat)\"") || rc=$?
else
out=$(printf '%s\n' "$payload" | ssh \
-o BatchMode=yes -o ConnectTimeout=10 $SSH_ARGS "$SSH_TARGET" \
"luna-send -n 1 '$uri' \"\$(cat)\"") || rc=$?
fi
else
if [ "$TIMEOUT" -gt 0 ]; then
out=$(timeout "$TIMEOUT" "$LUNASEND" -n 1 "$uri" "$payload") || rc=$?
else
out=$("$LUNASEND" -n 1 "$uri" "$payload") || rc=$?
fi
fi
if [ "$rc" -ne 0 ]; then
printf 'sam-cli: luna-send failed (rc=%s): %s\n' "$rc" "$out" >&2
return "$rc"
fi
print_out "$out"
return 0
}
ids_only() { # payload jq-expr -> prints ids, one per line
if command -v jq >/dev/null 2>&1; then
printf '%s\n' "$1" | jq -r "$2" 2>/dev/null && return 0
fi
# grep fallback: extract every "id":"..." field
printf '%s\n' "$1" | grep -o '"id":"[^"]*"' | sed 's/"id":"//; s/"$//'
}
# --- command dispatch -------------------------------------------------------
case "$CMD" in
launch)
[ "$#" -ge 1 ] || die "launch needs an app id, e.g. 'launch com.webos.app.enactbrowser'"
payload=$(json_base id "$1")
if [ "$#" -ge 2 ]; then payload=$(merge_json "$payload" "$2"); fi
run_luna launch "$payload" || exit $?
if [ "$WAIT" -gt 0 ]; then sleep "$WAIT"; fi
;;
close)
[ "$#" -ge 1 ] || die "close needs an app id or instanceId"
run_luna close "$(json_base id "$1")" || exit $?
if [ "$WAIT" -gt 0 ]; then sleep "$WAIT"; fi
;;
running)
payload=$(run_luna running '{}') || exit $?
if [ "$#" -ge 1 ] && [ "$1" = "--ids" ]; then
ids_only "$payload" '.running[]?.id'
else
printf '%s\n' "$payload"
fi
;;
list|list-apps|ls)
payload=$(run_luna listApps '{}') || exit $?
if [ "$#" -ge 1 ] && [ "$1" = "--ids" ]; then
ids_only "$payload" '.apps[]?.id'
else
printf '%s\n' "$payload"
fi
;;
status)
[ "$#" -ge 1 ] || die "status needs an appId, e.g. 'status com.webos.app.enactbrowser'"
run_luna getAppStatus "$(json_base appId "$1")" || exit $?
;;
info)
[ "$#" -ge 1 ] || die "info needs an app id, e.g. 'info com.webos.app.enactbrowser'"
run_luna getAppInfo "$(json_base id "$1")" || exit $?
;;
launch-points)
run_luna listLaunchPoints '{}' || exit $?
;;
manager-info)
run_luna managerInfo '{}' || exit $?
;;
*)
die "unknown command: $CMD (run 'sam-cli --help' for the list)"
;;
esac