- Refactor monolithic index.js (2646 lines) into src/{webex,integrations,
cards,flows,commands,services} modules; replace node-fetch/form-data
with native fetch/FormData; move all secrets to .env via dotenv
- Add dockerized remote SIW agent (docker/remote-agent/) with cross-arch
buildx packaging (arm64 Mac -> linux/amd64), idempotent install.sh
deploy bundle, and docker-free ZIP inspector for arch verification
- Bot hosts a WebSocket server; agent proxies SIW requests with a
per-request insecure:true flag, replacing the process-wide
NODE_TLS_REJECT_UNAUTHORIZED bypass
- Add ESLint flat config + Prettier, rewrite Dockerfile as non-root
multi-stage node:22-alpine build, README covering setup / deploy /
remote agent workflow
- Fix parseStoreArg to read trigger.prompt correctly (was indexing past
the framework's post-match slice); register /help as regex (string
matcher only compares the first token); switch catch-all to /.+/
(previous /.*/gim was stateful due to the g flag); remove
/fixDisplayNames command and its flow/card
Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
4.8 KiB
Bash
Executable file
135 lines
4.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Inspect a wbxprov-remote-agent deploy ZIP (or the tarball inside one) and
|
|
# report the actual OS/architecture of the Docker image it contains — WITHOUT
|
|
# needing docker installed. Reads the OCI/Docker image manifest directly out
|
|
# of the tarball.
|
|
#
|
|
# Use this before shipping a ZIP to a remote host to confirm you built the
|
|
# right platform. The main `package.sh` already runs `docker image inspect`
|
|
# on the image it just built, but this script gives you a completely
|
|
# independent check (no docker daemon involved at all) that also works on
|
|
# machines that never had docker on them.
|
|
#
|
|
# Usage:
|
|
# ./docker/remote-agent/inspect-bundle.sh <path-to-zip-or-tar.gz>
|
|
# ./docker/remote-agent/inspect-bundle.sh # auto-detect newest ZIP in dist/
|
|
#
|
|
# Exit codes:
|
|
# 0 bundle looks well-formed; arch/os printed
|
|
# 1 bad arguments / no bundle found
|
|
# 2 bundle is malformed or arch could not be read
|
|
#
|
|
# Optional env:
|
|
# EXPECTED_ARCH=amd64 die if the image's architecture does not match
|
|
# EXPECTED_OS=linux die if the image's OS does not match
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
DIST_DIR="$SCRIPT_DIR/dist"
|
|
|
|
RED=$'\033[0;31m'
|
|
GRN=$'\033[0;32m'
|
|
YLW=$'\033[1;33m'
|
|
RST=$'\033[0m'
|
|
|
|
log() { printf '%s[inspect]%s %s\n' "$GRN" "$RST" "$*"; }
|
|
warn() { printf '%s[inspect]%s %s\n' "$YLW" "$RST" "$*"; }
|
|
die() { printf '%s[inspect]%s %s\n' "$RED" "$RST" "$*" >&2; exit 2; }
|
|
|
|
command -v python3 >/dev/null 2>&1 || die "python3 is required (ships with macOS; on Linux: 'apt install python3')."
|
|
|
|
BUNDLE="${1:-}"
|
|
if [[ -z "$BUNDLE" ]]; then
|
|
# Auto-detect: newest ZIP in dist/
|
|
[[ -d "$DIST_DIR" ]] || { printf '%s\n' "usage: $0 <zip-or-tarball>"; exit 1; }
|
|
BUNDLE="$(ls -t "$DIST_DIR"/*.zip 2>/dev/null | head -n 1 || true)"
|
|
[[ -n "$BUNDLE" ]] || { printf '%s\n' "usage: $0 <zip-or-tarball> (no ZIPs in $DIST_DIR)"; exit 1; }
|
|
log "Auto-detected newest bundle: $BUNDLE"
|
|
fi
|
|
|
|
[[ -e "$BUNDLE" ]] || die "File not found: $BUNDLE"
|
|
|
|
# Resolve to the actual image tarball. If given a ZIP, extract to a tempdir
|
|
# and locate the tar.gz inside; otherwise assume the arg IS the tarball.
|
|
TMPDIR_INSPECT=""
|
|
cleanup() { [[ -n "$TMPDIR_INSPECT" ]] && rm -rf "$TMPDIR_INSPECT"; }
|
|
trap cleanup EXIT
|
|
|
|
case "$BUNDLE" in
|
|
*.zip)
|
|
command -v unzip >/dev/null 2>&1 || die "unzip is required to inspect a ZIP."
|
|
TMPDIR_INSPECT="$(mktemp -d)"
|
|
log "Extracting ZIP into temp dir for inspection..."
|
|
unzip -q "$BUNDLE" -d "$TMPDIR_INSPECT"
|
|
TARBALL="$(find "$TMPDIR_INSPECT" -maxdepth 3 -name '*.tar.gz' -type f | head -n 1 || true)"
|
|
[[ -n "$TARBALL" ]] || die "Could not find an image tarball (*.tar.gz) inside the ZIP."
|
|
;;
|
|
*.tar.gz|*.tgz|*.tar)
|
|
TARBALL="$BUNDLE"
|
|
;;
|
|
*)
|
|
die "Unrecognized bundle type: $BUNDLE (expected .zip, .tar.gz, .tgz, or .tar)"
|
|
;;
|
|
esac
|
|
|
|
log "Reading image manifest from: $(basename "$TARBALL")"
|
|
|
|
# The image tarball is a standard docker/OCI save. It contains:
|
|
# manifest.json -> lists image config path (per-tag entry)
|
|
# <config-sha>.json OR blobs/sha256/<sha> -> per-image config JSON
|
|
# We read manifest.json to find the config blob, then read the config
|
|
# blob's 'architecture' and 'os' fields. Everything happens in-memory via
|
|
# `tar -xO`, so nothing is written to disk.
|
|
|
|
DECOMPRESS="cat"
|
|
case "$TARBALL" in
|
|
*.gz|*.tgz) DECOMPRESS="gunzip -c" ;;
|
|
esac
|
|
|
|
MANIFEST_JSON="$($DECOMPRESS "$TARBALL" | tar -xO manifest.json 2>/dev/null || true)"
|
|
[[ -n "$MANIFEST_JSON" ]] || die "No manifest.json in tarball — is this really a 'docker save' bundle?"
|
|
|
|
CONFIG_PATH="$(printf '%s' "$MANIFEST_JSON" | python3 -c "
|
|
import json, sys
|
|
data = json.load(sys.stdin)
|
|
if not data:
|
|
sys.exit('empty manifest')
|
|
entry = data[0]
|
|
config = entry.get('Config') or entry.get('config')
|
|
if not config:
|
|
sys.exit('no Config in manifest entry')
|
|
print(config)
|
|
")"
|
|
|
|
CONFIG_JSON="$($DECOMPRESS "$TARBALL" | tar -xO "$CONFIG_PATH" 2>/dev/null || true)"
|
|
[[ -n "$CONFIG_JSON" ]] || die "Could not read config blob '$CONFIG_PATH' from tarball."
|
|
|
|
read -r ARCH OS <<<"$(printf '%s' "$CONFIG_JSON" | python3 -c "
|
|
import json, sys
|
|
d = json.load(sys.stdin)
|
|
print(d.get('architecture','?'), d.get('os','?'))
|
|
")"
|
|
|
|
log "Image tags: $(printf '%s' "$MANIFEST_JSON" | python3 -c "import json,sys; d=json.load(sys.stdin); print(', '.join(d[0].get('RepoTags') or ['(none)']))")"
|
|
log "Image OS: ${OS}"
|
|
log "Image arch: ${ARCH}"
|
|
|
|
FAIL=0
|
|
|
|
if [[ -n "${EXPECTED_ARCH:-}" && "${ARCH}" != "${EXPECTED_ARCH}" ]]; then
|
|
warn "Expected arch '${EXPECTED_ARCH}' but got '${ARCH}'."
|
|
FAIL=1
|
|
fi
|
|
|
|
if [[ -n "${EXPECTED_OS:-}" && "${OS}" != "${EXPECTED_OS}" ]]; then
|
|
warn "Expected OS '${EXPECTED_OS}' but got '${OS}'."
|
|
FAIL=1
|
|
fi
|
|
|
|
if (( FAIL != 0 )); then
|
|
die "Bundle does not match expected OS/arch. Rebuild with the correct --platform."
|
|
fi
|
|
|
|
log "OK — bundle looks well-formed."
|