agenshield 0.11.0 → 0.11.2-beta.530

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.
Files changed (3) hide show
  1. package/bin/agenshield +88 -134
  2. package/bin/install.sh +557 -0
  3. package/package.json +10 -8
package/bin/agenshield CHANGED
@@ -1,172 +1,126 @@
1
1
  #!/bin/sh
2
- # Launcher script for the `agenshield` npm wrapper package.
3
- # Resolves the correct platform-specific binary from optionalDependencies
4
- # and executes it with all arguments forwarded.
2
+ # Launcher for the `agenshield` npm wrapper package.
5
3
  #
6
- # Installed as `bin/agenshield` in the wrapper package.
4
+ # The wrapper is a thin BOOTSTRAP — it no longer bundles platform-specific
5
+ # binaries (those are too large for npm and now live on the agen-co/agenshield
6
+ # GitHub Release). On first use it runs the bundled `install.sh`, which
7
+ # downloads + verifies the signed artifact from GitHub Releases, then delegates
8
+ # to the installed `agenshield` binary. This keeps `npx agenshield …` working
9
+ # while the heavy signed artifacts ship via GitHub Releases instead of npm.
10
+ #
11
+ # Installed as `bin/agenshield` in the wrapper package (alongside `install.sh`).
7
12
 
8
13
  set -e
9
14
 
10
- # Detect platform
15
+ # ── Platform / arch (validation + diagnostics) ──────────────────────────────
11
16
  case "$(uname -s)" in
12
17
  Darwin) OS="darwin" ;;
13
18
  Linux) OS="linux" ;;
14
19
  *) echo "Unsupported platform: $(uname -s)" >&2; exit 1 ;;
15
20
  esac
16
-
17
21
  case "$(uname -m)" in
18
22
  x86_64|amd64) ARCH="x64" ;;
19
- aarch64|arm64) ARCH="arm64" ;;
20
- *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
23
+ aarch64|arm64) ARCH="arm64" ;;
24
+ *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
21
25
  esac
22
26
 
23
27
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
24
- PLATFORM_DIR="${OS}-${ARCH}"
25
- PKG="@agenshield/cli-${PLATFORM_DIR}"
26
- BIN=""
27
- PKG_ROOT=""
28
+ INSTALLER="$SCRIPT_DIR/install.sh"
29
+ HOME_BIN="${HOME}/.agenshield/bin/agenshield"
28
30
 
29
- # Wrapper version. Forwarded to the platform binary via
30
- # AGENSHIELD_NPX_PACKAGE_VERSION so getVersion() can report what the user
31
- # actually invoked (`npx agenshield@<v>`), regardless of whether the
32
- # resolved platform binary's embedded VERSION asset matches. Without this,
33
- # `npx agenshield@<new> upgrade` could compare against a stale binary's
34
- # baked-in version and silently no-op as "already at version <old>".
31
+ # ── Wrapper version AGENSHIELD_NPX_PACKAGE_VERSION ─────────────────────────
32
+ # Forwarded to the binary so getVersion() reports what the user actually invoked
33
+ # (`npx agenshield@<v>`), and so `upgrade` targets <v>. Load-bearing — keep it.
35
34
  WRAPPER_PKG_JSON="$SCRIPT_DIR/../package.json"
36
35
  WRAPPER_VERSION=""
37
36
  if [ -f "$WRAPPER_PKG_JSON" ]; then
38
- # Prefer node — guaranteed present for npx and immune to minified JSON.
39
- # The earlier awk-only parser silently returned the wrong field when
40
- # the package.json was on a single line (npm/yarn sometimes reformat
41
- # before publish), which let `npx agenshield@<v>` boot with an empty
42
- # WRAPPER_VERSION and silently no-op `upgrade` as "already at <old>".
43
37
  if command -v node >/dev/null 2>&1; then
44
38
  WRAPPER_VERSION=$(node -e "try{process.stdout.write(String(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||''))}catch(_){}" "$WRAPPER_PKG_JSON" 2>/dev/null || true)
45
39
  fi
46
- # awk fallback for non-node environments (direct shell invocation,
47
- # `agenshield` symlink dragged outside npx, etc.). Multi-line JSON only.
48
40
  if [ -z "$WRAPPER_VERSION" ]; then
41
+ # awk fallback for non-node environments (multi-line JSON only).
49
42
  WRAPPER_VERSION=$(/usr/bin/awk -F'"' '/"version"/ { print $4; exit }' "$WRAPPER_PKG_JSON" 2>/dev/null || true)
50
43
  fi
51
44
  fi
52
-
53
- # 1. Local dist layout: dist/npm/agenshield/bin/ → ../../darwin-arm64/
54
- DIST_DIR="$SCRIPT_DIR/../../${PLATFORM_DIR}"
55
- if [ -x "$DIST_DIR/bin/agenshield" ]; then
56
- BIN="$DIST_DIR/bin/agenshield"
57
- PKG_ROOT="$DIST_DIR"
45
+ if [ -n "$WRAPPER_VERSION" ]; then
46
+ export AGENSHIELD_NPX_PACKAGE_VERSION="$WRAPPER_VERSION"
58
47
  fi
59
48
 
60
- # 2. Flat-hoisted npm install (npm 7+, npx default):
61
- # node_modules/agenshield/bin/agenshield (this launcher)
62
- # node_modules/@agenshield/cli-<platform>/bin/agenshield (sibling, hoisted)
63
- if [ -z "$BIN" ]; then
64
- HOIST_DIR="$SCRIPT_DIR/../../$PKG"
65
- if [ -x "$HOIST_DIR/bin/agenshield" ]; then
66
- BIN="$HOIST_DIR/bin/agenshield"
67
- PKG_ROOT="$HOIST_DIR"
49
+ # ── Locate an already-installed agenshield binary ───────────────────────────
50
+ # Prefer ~/.agenshield/bin (tar install); then PATH (the .pkg symlinks
51
+ # /usr/local/bin/agenshield → /Library/AgenShield/bin). Skip this launcher's own
52
+ # directory so a globally-installed wrapper can't exec itself in a loop.
53
+ find_installed() {
54
+ if [ -x "$HOME_BIN" ]; then
55
+ echo "$HOME_BIN"
56
+ return 0
68
57
  fi
69
- fi
58
+ me_dir=$(cd "$SCRIPT_DIR" 2>/dev/null && pwd || echo "$SCRIPT_DIR")
59
+ OLDIFS="$IFS"
60
+ IFS=:
61
+ for p in $PATH; do
62
+ [ -z "$p" ] && continue
63
+ cand="$p/agenshield"
64
+ [ -x "$cand" ] || continue
65
+ real=$(readlink -f "$cand" 2>/dev/null || echo "$cand")
66
+ cdir=$(cd "$(dirname "$real")" 2>/dev/null && pwd || echo "$p")
67
+ if [ "$cdir" = "$me_dir" ]; then
68
+ continue
69
+ fi
70
+ IFS="$OLDIFS"
71
+ echo "$real"
72
+ return 0
73
+ done
74
+ IFS="$OLDIFS"
75
+ return 1
76
+ }
70
77
 
71
- # 3. Nested npm install (legacy / --legacy-peer-deps):
72
- # node_modules/agenshield/node_modules/@agenshield/cli-<platform>/...
73
- if [ -z "$BIN" ]; then
74
- NPM_DIR="$SCRIPT_DIR/../node_modules/$PKG"
75
- if [ -x "$NPM_DIR/bin/agenshield" ]; then
76
- BIN="$NPM_DIR/bin/agenshield"
77
- PKG_ROOT="$NPM_DIR"
78
+ # ── `install` run the full installer with the user's args ─────────────────
79
+ # install.sh handles the macOS .pkg / Linux tarball download + checksum verify
80
+ # + enrollment (--token/--cloud-url/--org) + service setup.
81
+ if [ "${1:-}" = "install" ]; then
82
+ shift
83
+ if [ ! -f "$INSTALLER" ]; then
84
+ echo "agenshield: bundled installer missing ($INSTALLER)." >&2
85
+ echo " Install AgenShield from your workspace: https://portal.frontegg.com" >&2
86
+ exit 1
78
87
  fi
88
+ # Pin the install to the wrapper's own version. `npx agenshield@<v> install`
89
+ # must install <v> — install.sh resolves GitHub `latest` ONLY when
90
+ # AGENSHIELD_VERSION is empty, so without this a pinned/prerelease invocation
91
+ # silently installed the latest STABLE instead. A user-supplied `--version`
92
+ # in "$@" still wins: install.sh's arg parser assigns AGENSHIELD_VERSION last.
93
+ # Empty WRAPPER_VERSION falls through to `latest` (unchanged behavior).
94
+ # Mirrors the bootstrap path below, which already pins via AGENSHIELD_VERSION.
95
+ exec env AGENSHIELD_VERSION="$WRAPPER_VERSION" sh "$INSTALLER" "$@"
79
96
  fi
80
97
 
81
- # Surface the wrapper version to the binary. Read by getVersion() in
82
- # libs/cli/src/utils/version.ts — preferred over the SEA asset so
83
- # `npx agenshield@<v>` always reports <v>, even when the resolved binary
84
- # was hoisted/cached/symlinked from a different version.
85
- if [ -n "$WRAPPER_VERSION" ]; then
86
- export AGENSHIELD_NPX_PACKAGE_VERSION="$WRAPPER_VERSION"
87
- fi
88
-
89
- if [ -n "$BIN" ]; then
90
- exec "$BIN" "$@"
91
- fi
92
-
93
- # 4. PATH fallback. Reached when optionalDeps silently failed to install
94
- # (most common cause: root-owned files in ~/.npm cache from a prior
95
- # `sudo npx`, so npm can't write the unpack and skips it without
96
- # erroring). Without this fallback, npx exits 1, then npm-exec
97
- # silently runs whatever `agenshield` is on PATH — typically a stale
98
- # .pkg-installed binary that PRECEDES the env-var support, so
99
- # `npx agenshield@<new>` reports the OLD installed version and the
100
- # upgrade silently no-ops as "already at version <old>".
101
- #
102
- # By searching PATH ourselves, we control the env that the resolved
103
- # binary inherits — AGENSHIELD_NPX_PACKAGE_VERSION is preserved
104
- # through `exec`, so any `getVersion()`-aware build (>=.428) reports
105
- # the wrapper version even when the cache is broken. Older builds
106
- # still see only their own embedded VERSION; nothing we can do for
107
- # those without reinstalling from the .pkg.
108
- #
109
- # Self-loop guard: skip any PATH entry that points back to this
110
- # launcher's own directory (would cause infinite recursion).
111
- ME_DIR="$SCRIPT_DIR"
112
- ME_REAL=""
113
- if command -v readlink >/dev/null 2>&1; then
114
- ME_REAL=$(readlink -f "$0" 2>/dev/null || echo "$0")
115
- else
116
- ME_REAL="$0"
117
- fi
118
- ME_DIR_REAL=$(cd "$(dirname "$ME_REAL")" 2>/dev/null && pwd || echo "$ME_DIR")
98
+ # ── Every other subcommand: ensure installed, then delegate ─────────────────
99
+ BIN="$(find_installed || true)"
119
100
 
120
- OLDIFS="$IFS"
121
- IFS=:
122
- for p in $PATH; do
123
- [ -z "$p" ] && continue
124
- cand="$p/agenshield"
125
- [ ! -x "$cand" ] && continue
126
- # Resolve symlinks before comparing — the .pkg installs
127
- # /usr/local/bin/agenshield as a symlink to /Library/AgenShield/bin/agenshield;
128
- # we want to match that binary, not the symlink.
129
- cand_real=$(readlink -f "$cand" 2>/dev/null || echo "$cand")
130
- cand_dir=$(cd "$(dirname "$cand_real")" 2>/dev/null && pwd || echo "$p")
131
- # Skip if the candidate is this same launcher (or in our own dir)
132
- if [ "$cand_dir" = "$ME_DIR_REAL" ] || [ "$cand_dir" = "$ME_DIR" ]; then
133
- continue
101
+ if [ -z "$BIN" ]; then
102
+ if [ ! -f "$INSTALLER" ]; then
103
+ echo "agenshield: not installed, and the bundled installer is missing." >&2
104
+ echo " Install AgenShield from your workspace: https://portal.frontegg.com" >&2
105
+ exit 1
134
106
  fi
135
- IFS="$OLDIFS"
136
- echo "agenshield: platform package missing from npm cache; falling back to $cand_real" >&2
137
- if [ -n "$WRAPPER_VERSION" ]; then
138
- echo "agenshield: forwarding requested version ${WRAPPER_VERSION} via AGENSHIELD_NPX_PACKAGE_VERSION" >&2
107
+ echo "agenshield: bootstrapping${WRAPPER_VERSION:+ v$WRAPPER_VERSION} from GitHub Releases…" >&2
108
+ # --skip-services lays the binary down quickly; `agenshield start` (or the
109
+ # .pkg postinstall) performs the privileged service setup. AGENSHIELD_VERSION
110
+ # pins the wrapper's version so npx version-pinning is honored.
111
+ if ! AGENSHIELD_VERSION="$WRAPPER_VERSION" sh "$INSTALLER" --skip-services >&2; then
112
+ echo "agenshield: bootstrap failed." >&2
113
+ echo " Get the install command from https://portal.frontegg.com" >&2
114
+ exit 1
139
115
  fi
140
- exec "$cand" "$@"
141
- done
142
- IFS="$OLDIFS"
116
+ BIN="$(find_installed || true)"
117
+ fi
143
118
 
144
- # Nothing on PATH either — give the operator an actionable diagnostic.
145
- echo "Platform package $PKG not found in npm cache, and no fallback agenshield binary on PATH." >&2
146
- echo "" >&2
147
- if [ -n "$WRAPPER_VERSION" ]; then
148
- echo " Wrapper: agenshield@${WRAPPER_VERSION}" >&2
149
- echo " Expected: $PKG@${WRAPPER_VERSION}" >&2
150
- echo "" >&2
119
+ if [ -z "$BIN" ]; then
120
+ echo "agenshield: install completed but no agenshield binary was found on PATH or in ~/.agenshield/bin." >&2
121
+ echo " Platform: ${OS}/${ARCH}" >&2
122
+ echo " Get the install command from https://portal.frontegg.com" >&2
123
+ exit 1
151
124
  fi
152
- echo "npm typically silently skips optionalDependencies when its cache has" >&2
153
- echo "files it can't write (most common cause: a prior 'sudo npx' left some" >&2
154
- echo "files root-owned). Recovery:" >&2
155
- echo "" >&2
156
- echo " sudo chown -R \"\$(id -u):\$(id -g)\" ~/.npm" >&2
157
- echo " npm cache clean --force" >&2
158
- echo " rm -rf ~/.npm/_npx" >&2
159
- echo " npx --yes agenshield@<version> upgrade" >&2
160
- echo "" >&2
161
- echo "Or install the platform binary directly from npm and bypass npx:" >&2
162
- echo "" >&2
163
- echo " TARBALL=\$(npm view ${PKG}@<version> dist.tarball)" >&2
164
- echo " curl -fSL -o /tmp/agenshield.tgz \"\$TARBALL\"" >&2
165
- echo " tar -xzf /tmp/agenshield.tgz -C /tmp" >&2
166
- echo " sudo installer -pkg /tmp/package/AgenShield.pkg -target /" >&2
167
- echo "" >&2
168
- echo "Supported platforms:" >&2
169
- echo " - darwin/arm64 (Apple Silicon)" >&2
170
- echo " - darwin/x64 (Intel Mac)" >&2
171
- echo " - linux/x64 (Linux x86_64)" >&2
172
- exit 1
125
+
126
+ exec "$BIN" "$@"
package/bin/install.sh ADDED
@@ -0,0 +1,557 @@
1
+ #!/bin/sh
2
+ # AgenShield Installer
3
+ #
4
+ # Downloads and installs the AgenShield SEA binaries for the current platform.
5
+ #
6
+ # Multi-binary layout:
7
+ # ~/.agenshield/bin/agenshield (CLI — on PATH)
8
+ # ~/.agenshield/libexec/agenshield-daemon (Daemon — internal)
9
+ # ~/.agenshield/libexec/agenshield-broker (Broker — internal)
10
+ # ~/.agenshield/lib/v{VERSION}/native/ (Native addons)
11
+ #
12
+ # Usage:
13
+ # curl -fsSL https://get.agenshield.com/install.sh | sh
14
+ # curl -fsSL https://get.agenshield.com/install.sh | bash -s -- --cloud-url https://cloud.example.com --org my-org
15
+ #
16
+ # CLI arguments (override environment variables):
17
+ # --cloud-url <url> Cloud/policy server URL for automatic enrollment
18
+ # --org <id> Org client ID for MDM enrollment (device code flow on daemon start)
19
+ # --token <token> Enrollment token for automatic cloud setup (MDM)
20
+ # --version <ver> Install a specific version (default: latest)
21
+ # --skip-services Skip macOS LaunchDaemon/LaunchAgent install
22
+ # -h, --help Show usage and exit
23
+ #
24
+ # Environment variables:
25
+ # AGENSHIELD_VERSION - Install a specific version (default: latest)
26
+ # AGENSHIELD_GITHUB_REPO - GitHub repo for downloads (default: agen-co/agenshield)
27
+ # AGENSHIELD_TOKEN - Enrollment token for automatic cloud setup (MDM)
28
+ # AGENSHIELD_CLOUD_URL - Cloud API URL for automatic setup (requires AGENSHIELD_TOKEN or AGENSHIELD_ORG)
29
+ # AGENSHIELD_ORG - Org client ID for MDM enrollment (device code flow on daemon start)
30
+ # AGENSHIELD_BASE_URL - Base URL for downloading archive and checksums (overrides GitHub)
31
+ # AGENSHIELD_SKIP_SERVICES - Set to "1" to skip macOS LaunchDaemon/LaunchAgent install
32
+ #
33
+ set -e
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Configuration
37
+ # ---------------------------------------------------------------------------
38
+
39
+ GITHUB_REPO="${AGENSHIELD_GITHUB_REPO:-agen-co/agenshield}"
40
+ CLI_BINARY="agenshield"
41
+
42
+ # Colors (disabled if not a terminal or NO_COLOR is set)
43
+ if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
44
+ RED='\033[0;31m'
45
+ GREEN='\033[0;32m'
46
+ YELLOW='\033[0;33m'
47
+ CYAN='\033[0;36m'
48
+ DIM='\033[2m'
49
+ BOLD='\033[1m'
50
+ RESET='\033[0m'
51
+ else
52
+ RED='' GREEN='' YELLOW='' CYAN='' DIM='' BOLD='' RESET=''
53
+ fi
54
+
55
+ # ---------------------------------------------------------------------------
56
+ # Helpers
57
+ # ---------------------------------------------------------------------------
58
+
59
+ info() { printf "${CYAN}info${RESET} %s\n" "$1"; }
60
+ ok() { printf "${GREEN} ok${RESET} %s\n" "$1"; }
61
+ warn() { printf "${YELLOW}warn${RESET} %s\n" "$1"; }
62
+ error() { printf "${RED}error${RESET} %s\n" "$1" >&2; }
63
+ die() { error "$1"; exit 1; }
64
+
65
+ # Detect the platform
66
+ detect_platform() {
67
+ OS="$(uname -s)"
68
+ case "$OS" in
69
+ Darwin) PLATFORM="darwin" ;;
70
+ Linux) PLATFORM="linux" ;;
71
+ *) die "Unsupported operating system: $OS" ;;
72
+ esac
73
+ }
74
+
75
+ # Detect the architecture
76
+ detect_arch() {
77
+ ARCH="$(uname -m)"
78
+ case "$ARCH" in
79
+ x86_64|amd64) ARCH="x64" ;;
80
+ aarch64|arm64) ARCH="arm64" ;;
81
+ *) die "Unsupported architecture: $ARCH" ;;
82
+ esac
83
+ }
84
+
85
+ # Find a download utility
86
+ detect_downloader() {
87
+ if command -v curl >/dev/null 2>&1; then
88
+ DOWNLOAD_CMD="curl"
89
+ elif command -v wget >/dev/null 2>&1; then
90
+ DOWNLOAD_CMD="wget"
91
+ else
92
+ die "Neither curl nor wget found. Please install one."
93
+ fi
94
+ }
95
+
96
+ # Download a URL to a file
97
+ download() {
98
+ url="$1"
99
+ dest="$2"
100
+ if [ "$DOWNLOAD_CMD" = "curl" ]; then
101
+ curl -fsSL -o "$dest" "$url"
102
+ else
103
+ wget -qO "$dest" "$url"
104
+ fi
105
+ }
106
+
107
+ # Download a URL and print to stdout
108
+ download_text() {
109
+ url="$1"
110
+ if [ "$DOWNLOAD_CMD" = "curl" ]; then
111
+ curl -fsSL "$url"
112
+ else
113
+ wget -qO- "$url"
114
+ fi
115
+ }
116
+
117
+ # Resolve the release channel: stable (default) | alpha | beta.
118
+ # Precedence mirrors the campaign install script + the CLI's resolveReleaseChannel:
119
+ # AGENSHIELD_CHANNEL wins, then AGENSHIELD_CLIENT_ALPHA/BETA=true.
120
+ resolve_channel() {
121
+ case "${AGENSHIELD_CHANNEL:-}" in
122
+ alpha) echo alpha; return ;;
123
+ beta) echo beta; return ;;
124
+ stable) echo stable; return ;;
125
+ esac
126
+ [ "${AGENSHIELD_CLIENT_ALPHA:-}" = "true" ] && { echo alpha; return; }
127
+ [ "${AGENSHIELD_CLIENT_BETA:-}" = "true" ] && { echo beta; return; }
128
+ echo stable
129
+ }
130
+
131
+ # Resolve the latest version for the channel from the GitHub Releases API.
132
+ # stable → /releases/latest (never a pre-release). alpha/beta → list recent
133
+ # releases (newest first) and take the first tag carrying the channel's
134
+ # pre-release identifier (-alpha / -beta), which release.yml only ever
135
+ # publishes as a pre-release. `/releases/latest` cannot see pre-releases, which
136
+ # is why alpha/beta were silently downgraded to stable before this fix.
137
+ resolve_latest_version() {
138
+ CHANNEL=$(resolve_channel)
139
+
140
+ if [ "$CHANNEL" = "stable" ]; then
141
+ info "Checking latest stable version..."
142
+ LATEST_JSON=$(download_text "https://api.github.com/repos/$GITHUB_REPO/releases/latest" 2>/dev/null || true)
143
+ [ -z "$LATEST_JSON" ] && die "Failed to query GitHub Releases API. Check your internet connection."
144
+ VERSION=$(echo "$LATEST_JSON" | grep '"tag_name"' | head -1 | sed 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\(v[^"]*\)".*/\1/' | sed 's/^v//')
145
+ else
146
+ info "Checking latest ${CHANNEL} pre-release..."
147
+ LIST_JSON=$(download_text "https://api.github.com/repos/$GITHUB_REPO/releases?per_page=30" 2>/dev/null || true)
148
+ [ -z "$LIST_JSON" ] && die "Failed to query GitHub Releases API. Check your internet connection."
149
+ # grep -o pulls every tag_name (newest-first, both compact + pretty JSON);
150
+ # filter to the channel marker and take the newest.
151
+ VERSION=$(echo "$LIST_JSON" \
152
+ | grep -oE '"tag_name"[[:space:]]*:[[:space:]]*"v[^"]*"' \
153
+ | sed -E 's/.*"(v[^"]*)".*/\1/' \
154
+ | grep -- "-${CHANNEL}" \
155
+ | head -1 | sed 's/^v//')
156
+ fi
157
+
158
+ if [ -z "$VERSION" ]; then
159
+ die "Could not determine latest ${CHANNEL} version from GitHub."
160
+ fi
161
+ }
162
+
163
+ # Verify SHA-256 checksum
164
+ verify_checksum() {
165
+ archive="$1"
166
+ expected_checksum="$2"
167
+
168
+ if command -v shasum >/dev/null 2>&1; then
169
+ actual=$(shasum -a 256 "$archive" | awk '{print $1}')
170
+ elif command -v sha256sum >/dev/null 2>&1; then
171
+ actual=$(sha256sum "$archive" | awk '{print $1}')
172
+ else
173
+ warn "Neither shasum nor sha256sum found — skipping checksum verification"
174
+ return 0
175
+ fi
176
+
177
+ if [ "$actual" != "$expected_checksum" ]; then
178
+ die "Checksum mismatch!\n Expected: $expected_checksum\n Actual: $actual"
179
+ fi
180
+ }
181
+
182
+ # ---------------------------------------------------------------------------
183
+ # Main
184
+ # ---------------------------------------------------------------------------
185
+
186
+ main() {
187
+ # Parse CLI arguments (override environment variables)
188
+ while [ $# -gt 0 ]; do
189
+ case "$1" in
190
+ --cloud-url)
191
+ AGENSHIELD_CLOUD_URL="$2"; shift 2 ;;
192
+ --cloud-url=*)
193
+ AGENSHIELD_CLOUD_URL="${1#*=}"; shift ;;
194
+ --org)
195
+ AGENSHIELD_ORG="$2"; shift 2 ;;
196
+ --org=*)
197
+ AGENSHIELD_ORG="${1#*=}"; shift ;;
198
+ --token)
199
+ AGENSHIELD_TOKEN="$2"; shift 2 ;;
200
+ --token=*)
201
+ AGENSHIELD_TOKEN="${1#*=}"; shift ;;
202
+ --version)
203
+ AGENSHIELD_VERSION="$2"; shift 2 ;;
204
+ --version=*)
205
+ AGENSHIELD_VERSION="${1#*=}"; shift ;;
206
+ --skip-services)
207
+ AGENSHIELD_SKIP_SERVICES="1"; shift ;;
208
+ -h|--help)
209
+ printf "AgenShield Installer\n\n"
210
+ printf "Usage:\n"
211
+ printf " curl -fsSL https://get.agenshield.com/install.sh | sh\n"
212
+ printf " curl -fsSL https://get.agenshield.com/install.sh | bash -s -- [OPTIONS]\n\n"
213
+ printf "Options:\n"
214
+ printf " --cloud-url <url> Cloud/policy server URL for automatic enrollment\n"
215
+ printf " --org <id> Org client ID for MDM enrollment\n"
216
+ printf " --token <token> Enrollment token for automatic cloud setup\n"
217
+ printf " --version <ver> Install a specific version (default: latest)\n"
218
+ printf " --skip-services Skip macOS LaunchDaemon/LaunchAgent install\n"
219
+ printf " -h, --help Show this help message\n"
220
+ exit 0
221
+ ;;
222
+ *)
223
+ warn "Unknown option: $1"; shift ;;
224
+ esac
225
+ done
226
+
227
+ # Validate: --org requires --cloud-url
228
+ if [ -n "${AGENSHIELD_ORG:-}" ] && [ -z "${AGENSHIELD_CLOUD_URL:-}" ]; then
229
+ die "--org requires --cloud-url to be specified"
230
+ fi
231
+
232
+ printf "\n${BOLD}AgenShield Installer${RESET}\n\n"
233
+
234
+ detect_platform
235
+ detect_arch
236
+ detect_downloader
237
+
238
+ info "Platform: $PLATFORM/$ARCH"
239
+
240
+ # Determine version
241
+ VERSION="${AGENSHIELD_VERSION:-}"
242
+ if [ -z "$VERSION" ]; then
243
+ resolve_latest_version
244
+ fi
245
+ info "Version: $VERSION"
246
+
247
+ # ── macOS: use .pkg installer ────────────────────────────────────────────
248
+ if [ "$PLATFORM" = "darwin" ]; then
249
+ # AgenShield's macOS distribution is Apple Silicon (arm64) only — the
250
+ # release pipeline builds and publishes a single arm64 .pkg. On an
251
+ # Intel Mac, ARCH resolves to x64 and the .pkg URL would 404, then
252
+ # silently fall through to a (also-nonexistent) x64 .tar.gz — a
253
+ # confusing failure that looks like a broken release. Fail fast with a
254
+ # clear, actionable message instead.
255
+ if [ "$ARCH" != "arm64" ]; then
256
+ die "AgenShield for macOS currently supports Apple Silicon (arm64) only — detected '$ARCH'. Intel Macs are not yet supported."
257
+ fi
258
+ PKG_NAME="AgenShield-${VERSION}-${ARCH}.pkg"
259
+ BASE_URL="${AGENSHIELD_BASE_URL:-}"
260
+ if [ -n "$BASE_URL" ]; then
261
+ PKG_URL="${BASE_URL}/${PKG_NAME}"
262
+ else
263
+ PKG_URL="https://github.com/$GITHUB_REPO/releases/download/v${VERSION}/${PKG_NAME}"
264
+ fi
265
+
266
+ TMPDIR_INSTALL="$(mktemp -d)"
267
+ trap 'rm -rf "$TMPDIR_INSTALL"' EXIT
268
+
269
+ info "Downloading macOS installer: $PKG_NAME..."
270
+ if download "$PKG_URL" "$TMPDIR_INSTALL/$PKG_NAME" 2>/dev/null; then
271
+ ok "Downloaded $PKG_NAME"
272
+
273
+ # Write enrollment tokens to a file for the .pkg postinstall to consume.
274
+ # macOS `installer` does NOT propagate env vars to pre/post install scripts,
275
+ # so we use a well-known file as the handoff mechanism.
276
+ ENROLL_HOME="${HOME}/.agenshield"
277
+ if [ -n "${AGENSHIELD_TOKEN:-}" ] && [ -n "${AGENSHIELD_CLOUD_URL:-}" ]; then
278
+ mkdir -p "$ENROLL_HOME"
279
+ cat > "$ENROLL_HOME/pending-enrollment.json" <<EEOF
280
+ {"token":"${AGENSHIELD_TOKEN}","cloudUrl":"${AGENSHIELD_CLOUD_URL}","org":"${AGENSHIELD_ORG:-}"}
281
+ EEOF
282
+ chmod 600 "$ENROLL_HOME/pending-enrollment.json"
283
+ fi
284
+
285
+ if [ -t 0 ] && [ -t 1 ]; then
286
+ # Interactive: open the macOS Installer GUI
287
+ info "Opening installer..."
288
+ open "$TMPDIR_INSTALL/$PKG_NAME"
289
+ printf "\n${GREEN}${BOLD}macOS Installer opened.${RESET}\n"
290
+ printf " Follow the prompts to complete installation.\n"
291
+ printf " After installation completes, run: ${CYAN}agenshield start${RESET}\n\n"
292
+ exit 0
293
+ else
294
+ # Non-interactive (MDM/scripted): use command-line installer
295
+ info "Installing (headless mode)..."
296
+
297
+ # Show live postinstall progress by tailing the install log
298
+ sudo pkill -f "tail -F /var/log/agenshield-install.log" 2>/dev/null || true
299
+ sudo truncate -s 0 /var/log/agenshield-install.log 2>/dev/null || \
300
+ sudo sh -c 'echo -n "" > /var/log/agenshield-install.log' 2>/dev/null || true
301
+ sudo tail -F /var/log/agenshield-install.log 2>/dev/null | sed 's/^/ │ /' &
302
+ TAIL_PID=$!
303
+ _cleanup_tail() {
304
+ sudo pkill -P $$ 2>/dev/null || true
305
+ sudo pkill -f "tail -F /var/log/agenshield-install.log" 2>/dev/null || true
306
+ }
307
+ trap '_cleanup_tail; rm -rf "$TMPDIR_INSTALL"' EXIT INT TERM
308
+
309
+ sudo installer -pkg "$TMPDIR_INSTALL/$PKG_NAME" -target /
310
+
311
+ sleep 1
312
+ _cleanup_tail
313
+ ok "Package installed"
314
+
315
+ # The postinstall script reads pending-enrollment.json and enrolls.
316
+ # Wait for daemon to become ready (postinstall bootstraps LaunchDaemon).
317
+ if [ -n "${AGENSHIELD_TOKEN:-}" ] && [ -n "${AGENSHIELD_CLOUD_URL:-}" ]; then
318
+ info "Waiting for daemon to start..."
319
+ READY=0
320
+ for i in $(seq 1 20); do
321
+ if curl -sf http://127.0.0.1:5200/api/health >/dev/null 2>&1; then
322
+ READY=1
323
+ break
324
+ fi
325
+ sleep 1
326
+ done
327
+
328
+ if [ "$READY" = "1" ]; then
329
+ ok "Daemon is running"
330
+ # Login is NOT required to install or run AgenShield. The daemon is
331
+ # device-enrolled (campaign token) and enforces the synced policy
332
+ # bundle without a user session. The user signs in LATER from the
333
+ # AgenShield menubar to attach their account to this installation.
334
+ info "Sign in any time from the AgenShield menubar to link your account."
335
+ else
336
+ warn "Daemon did not become ready in 20s. Run: agenshield start"
337
+ fi
338
+ fi
339
+
340
+ printf "\n${GREEN}${BOLD}AgenShield installed!${RESET}\n\n"
341
+ printf " Installation directory: ${DIM}/Library/AgenShield/${RESET}\n"
342
+ printf " Dashboard: ${CYAN}http://localhost:5200${RESET}\n"
343
+ printf " ${BOLD}Sign in${RESET} from the AgenShield menubar to link your account.\n\n"
344
+ exit 0
345
+ fi
346
+ else
347
+ warn "Could not download .pkg installer — falling back to tar.gz install"
348
+ fi
349
+ fi
350
+
351
+ # ── Linux / fallback: tar.gz install ─────────────────────────────────────
352
+
353
+ # Construct download URL
354
+ ARCHIVE_NAME="agenshield-${VERSION}-${PLATFORM}-${ARCH}.tar.gz"
355
+ BASE_URL="${AGENSHIELD_BASE_URL:-}"
356
+ if [ -n "$BASE_URL" ]; then
357
+ DOWNLOAD_URL="${BASE_URL}/${ARCHIVE_NAME}"
358
+ CHECKSUM_URL="${BASE_URL}/checksums.sha256"
359
+ info "Using base URL: $BASE_URL"
360
+ else
361
+ DOWNLOAD_URL="https://github.com/$GITHUB_REPO/releases/download/v${VERSION}/${ARCHIVE_NAME}"
362
+ CHECKSUM_URL="https://github.com/$GITHUB_REPO/releases/download/v${VERSION}/checksums.sha256"
363
+ fi
364
+
365
+ # Detect script directory for local archive mode
366
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
367
+
368
+ # Create temp directory
369
+ TMPDIR_INSTALL="$(mktemp -d)"
370
+ trap 'rm -rf "$TMPDIR_INSTALL"' EXIT
371
+
372
+ # Download or copy archive
373
+ if [ -f "$SCRIPT_DIR/$ARCHIVE_NAME" ]; then
374
+ info "Using local archive: $SCRIPT_DIR/$ARCHIVE_NAME"
375
+ cp "$SCRIPT_DIR/$ARCHIVE_NAME" "$TMPDIR_INSTALL/$ARCHIVE_NAME"
376
+ ok "Copied local $ARCHIVE_NAME"
377
+ else
378
+ info "Downloading $ARCHIVE_NAME..."
379
+ download "$DOWNLOAD_URL" "$TMPDIR_INSTALL/$ARCHIVE_NAME" || \
380
+ die "Failed to download $DOWNLOAD_URL"
381
+ ok "Downloaded $ARCHIVE_NAME"
382
+ fi
383
+
384
+ # Verify checksum
385
+ info "Verifying checksum..."
386
+ if [ -f "$SCRIPT_DIR/checksums.sha256" ]; then
387
+ CHECKSUMS=$(cat "$SCRIPT_DIR/checksums.sha256")
388
+ else
389
+ CHECKSUMS=$(download_text "$CHECKSUM_URL" 2>/dev/null || true)
390
+ fi
391
+ if [ -n "$CHECKSUMS" ]; then
392
+ EXPECTED=$(echo "$CHECKSUMS" | grep "$ARCHIVE_NAME" | awk '{print $1}')
393
+ if [ -n "$EXPECTED" ]; then
394
+ verify_checksum "$TMPDIR_INSTALL/$ARCHIVE_NAME" "$EXPECTED"
395
+ ok "Checksum verified"
396
+ else
397
+ warn "Archive not found in checksums file — skipping verification"
398
+ fi
399
+ else
400
+ warn "Could not download checksums — skipping verification"
401
+ fi
402
+
403
+ # ── Installation directories ─────────────────────────────────────────────
404
+ INSTALL_DIR="${AGENSHIELD_INSTALL_DIR:-$HOME/.agenshield}"
405
+ BIN_DIR="$INSTALL_DIR/bin"
406
+ LIB_DIR="$INSTALL_DIR/lib/v${VERSION}"
407
+
408
+ mkdir -p "$BIN_DIR"
409
+ mkdir -p "$INSTALL_DIR/libexec"
410
+ mkdir -p "$INSTALL_DIR/logs"
411
+
412
+ # Extract archive to temp directory
413
+ EXTRACT_DIR="$TMPDIR_INSTALL/extract"
414
+ mkdir -p "$EXTRACT_DIR"
415
+ info "Extracting..."
416
+ tar -xzf "$TMPDIR_INSTALL/$ARCHIVE_NAME" -C "$EXTRACT_DIR"
417
+
418
+ # ── Copy binaries (no execution — avoids SentinelOne process-tree kill) ──
419
+ # CLI binary → bin/
420
+ if [ -f "$EXTRACT_DIR/$CLI_BINARY" ]; then
421
+ cp "$EXTRACT_DIR/$CLI_BINARY" "$BIN_DIR/$CLI_BINARY"
422
+ chmod 755 "$BIN_DIR/$CLI_BINARY"
423
+ ok "Installed $CLI_BINARY → $BIN_DIR/"
424
+ fi
425
+
426
+ # Daemon + broker → libexec/
427
+ for BINARY in agenshield-daemon agenshield-broker; do
428
+ if [ -f "$EXTRACT_DIR/$BINARY" ]; then
429
+ cp "$EXTRACT_DIR/$BINARY" "$INSTALL_DIR/libexec/$BINARY"
430
+ chmod 755 "$INSTALL_DIR/libexec/$BINARY"
431
+ ok "Installed $BINARY → libexec/"
432
+ fi
433
+ done
434
+
435
+ # Native modules
436
+ if [ -d "$EXTRACT_DIR/native" ]; then
437
+ mkdir -p "$LIB_DIR/native"
438
+ cp "$EXTRACT_DIR/native/"* "$LIB_DIR/native/" 2>/dev/null || true
439
+ ok "Installed native modules"
440
+ fi
441
+
442
+ # Worker scripts
443
+ if [ -d "$EXTRACT_DIR/workers" ]; then
444
+ mkdir -p "$LIB_DIR/workers"
445
+ cp "$EXTRACT_DIR/workers/"* "$LIB_DIR/workers/" 2>/dev/null || true
446
+ ok "Installed worker scripts"
447
+ fi
448
+
449
+ # Interceptor scripts
450
+ if [ -d "$EXTRACT_DIR/interceptor" ]; then
451
+ mkdir -p "$LIB_DIR/interceptor"
452
+ cp "$EXTRACT_DIR/interceptor/"* "$LIB_DIR/interceptor/" 2>/dev/null || true
453
+ ok "Installed interceptor scripts"
454
+ fi
455
+
456
+ # Client scripts
457
+ if [ -d "$EXTRACT_DIR/client" ]; then
458
+ mkdir -p "$LIB_DIR/client"
459
+ cp "$EXTRACT_DIR/client/"* "$LIB_DIR/client/" 2>/dev/null || true
460
+ ok "Installed client scripts"
461
+ fi
462
+
463
+ # UI assets
464
+ if [ -d "$EXTRACT_DIR/ui-assets" ]; then
465
+ mkdir -p "$LIB_DIR/ui-assets"
466
+ cp -R "$EXTRACT_DIR/ui-assets/." "$LIB_DIR/ui-assets/" 2>/dev/null || true
467
+ ok "Installed UI assets"
468
+ fi
469
+
470
+ # macOS menu bar app
471
+ if [ -d "$EXTRACT_DIR/AgenShield.app" ]; then
472
+ APPS_DIR="$INSTALL_DIR/apps"
473
+ mkdir -p "$APPS_DIR"
474
+ rm -rf "$APPS_DIR/AgenShield.app" 2>/dev/null || true
475
+ cp -R "$EXTRACT_DIR/AgenShield.app" "$APPS_DIR/AgenShield.app"
476
+ ok "Installed AgenShield.app → apps/"
477
+ fi
478
+
479
+ # Write version stamp
480
+ mkdir -p "$LIB_DIR"
481
+ echo "${VERSION}:wius" > "$LIB_DIR/.extracted"
482
+
483
+ # ── macOS: remove quarantine from all installed files ───────────────────
484
+ if [ "$PLATFORM" = "darwin" ]; then
485
+ xattr -dr com.apple.quarantine "$INSTALL_DIR" 2>/dev/null || true
486
+ ok "Removed quarantine attributes"
487
+ fi
488
+
489
+ # ── Add to PATH ────────────────────────────────────────────────────────
490
+ SHELL_NAME="$(basename "${SHELL:-/bin/sh}")"
491
+ case "$SHELL_NAME" in
492
+ zsh) RC_FILE="$HOME/.zshrc" ;;
493
+ bash)
494
+ if [ -f "$HOME/.bash_profile" ]; then
495
+ RC_FILE="$HOME/.bash_profile"
496
+ else
497
+ RC_FILE="$HOME/.bashrc"
498
+ fi
499
+ ;;
500
+ *) RC_FILE="$HOME/.profile" ;;
501
+ esac
502
+
503
+ if [ -f "$RC_FILE" ] && grep -q '.agenshield/bin' "$RC_FILE" 2>/dev/null; then
504
+ ok "PATH already configured in $RC_FILE"
505
+ else
506
+ printf '\n# AgenShield CLI\nexport PATH="%s:$PATH"\n' "$BIN_DIR" >> "$RC_FILE"
507
+ ok "Added PATH to $RC_FILE"
508
+ fi
509
+
510
+ # ── Complete installation (enrollment + service setup) ────────────────
511
+ export PATH="$BIN_DIR:$PATH"
512
+
513
+ # Enrollment only — binaries are already extracted above.
514
+ # Use --pkg-mode to skip binary re-installation and only do cloud enrollment.
515
+ if [ -n "${AGENSHIELD_TOKEN:-}" ] && [ -n "${AGENSHIELD_CLOUD_URL:-}" ]; then
516
+ INSTALL_CMD="\"$BIN_DIR/agenshield\" install --pkg-mode"
517
+ INSTALL_CMD="$INSTALL_CMD --cloud-url \"$AGENSHIELD_CLOUD_URL\""
518
+ INSTALL_CMD="$INSTALL_CMD --token \"$AGENSHIELD_TOKEN\""
519
+ if [ -n "${AGENSHIELD_ORG:-}" ]; then
520
+ INSTALL_CMD="$INSTALL_CMD --org \"$AGENSHIELD_ORG\""
521
+ fi
522
+ info "Running cloud enrollment..."
523
+ eval $INSTALL_CMD || warn "Cloud enrollment failed (you can retry with: agenshield install --pkg-mode --token ... --cloud-url ...)"
524
+ fi
525
+
526
+ # Re-ensure native modules are in place
527
+ if [ -d "$EXTRACT_DIR/native" ] && [ ! -d "$LIB_DIR/native" ]; then
528
+ mkdir -p "$LIB_DIR/native"
529
+ cp "$EXTRACT_DIR/native/"* "$LIB_DIR/native/" 2>/dev/null || true
530
+ ok "Re-installed native modules"
531
+ fi
532
+
533
+ # Start daemon — triggers /Applications copy + LaunchDaemon/Agent install.
534
+ # Installation must NOT require login: the daemon starts device-enrolled and
535
+ # enforces the synced policy bundle WITHOUT a user session. Setting
536
+ # AGENSHIELD_SKIP_LOGIN=1 (honored by libs/cli/src/utils/login.ts) suppresses
537
+ # the blocking browser-OAuth wait; the user signs in LATER from the AgenShield
538
+ # menubar. `agenshield start` still starts the daemon + installs services —
539
+ # only the trailing login step is skipped.
540
+ #
541
+ # Honor --skip-services / AGENSHIELD_SKIP_SERVICES: the .pkg postinstall has
542
+ # already installed + bootstrapped the LaunchDaemon, so skip the redundant
543
+ # in-pipe `agenshield start` when the operator opted out of service management.
544
+ if [ "${AGENSHIELD_SKIP_SERVICES:-0}" = "1" ]; then
545
+ info "Skipping 'agenshield start' (--skip-services); the LaunchDaemon manages the daemon."
546
+ else
547
+ info "Starting AgenShield daemon (login deferred to the menubar)..."
548
+ AGENSHIELD_SKIP_LOGIN=1 "$BIN_DIR/agenshield" start || warn "Daemon start failed (you can retry with: agenshield start)"
549
+ fi
550
+
551
+ printf "\n${GREEN}${BOLD}AgenShield installed and running!${RESET}\n\n"
552
+ printf " Installation directory: ${DIM}%s${RESET}\n" "$INSTALL_DIR"
553
+ printf " Dashboard: ${CYAN}http://localhost:5200${RESET}\n"
554
+ printf " ${BOLD}Sign in${RESET} from the AgenShield menubar to link your account.\n\n"
555
+ }
556
+
557
+ main "$@"
package/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "agenshield",
3
- "version": "0.11.0",
3
+ "version": "0.11.2-beta.530",
4
4
  "description": "AgenShield — AI Agent Security Platform",
5
5
  "bin": {
6
6
  "agenshield": "bin/agenshield"
7
7
  },
8
- "optionalDependencies": {
9
- "@agenshield/cli-darwin-arm64": "0.11.0",
10
- "@agenshield/cli-darwin-x64": "0.11.0",
11
- "@agenshield/cli-linux-x64": "0.11.0"
12
- },
13
8
  "files": [
14
9
  "bin/"
15
10
  ],
16
- "license": "MIT",
11
+ "license": "Apache-2.0",
17
12
  "repository": {
18
13
  "type": "git",
19
- "url": "https://github.com/agen-co/agen-shield.git"
14
+ "url": "git+https://github.com/agen-co/agenshield.git"
15
+ },
16
+ "homepage": "https://agen.co/shield",
17
+ "bugs": {
18
+ "url": "https://github.com/agen-co/agenshield/issues"
19
+ },
20
+ "engines": {
21
+ "node": ">=22.0.0"
20
22
  }
21
23
  }