223 lines
8.1 KiB
Bash
Executable File
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
|