@aporthq/aport-agent-guardrails 1.0.23 → 1.0.24

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 CHANGED
@@ -124,6 +124,9 @@ Install via `npx @aporthq/aport-agent-guardrails <framework>` (or choose when pr
124
124
  ```bash
125
125
  npx @aporthq/aport-agent-guardrails
126
126
  # or: npx @aporthq/aport-agent-guardrails openclaw | cursor | claude-code | langchain | crewai | deerflow | n8n
127
+ # optional mode flags (all frameworks):
128
+ # --mode=api --api-url=https://api.aport.io
129
+ # --mode=local
127
130
  ```
128
131
 
129
132
  **Python (LangChain, CrewAI, or DeerFlow):** Use the Python CLI directly via `uvx` or an installed package:
@@ -141,6 +144,8 @@ Then install the framework-specific Python package and follow the printed integr
141
144
 
142
145
  This runs the **passport wizard** and writes config for your framework. Follow the **next steps** printed at the end (e.g. restart Cursor; or for CrewAI: by default install `aport-agent-guardrails-crewai` for released CrewAI, or opt into native-provider mode if your CrewAI build supports it).
143
146
 
147
+ **Guardrail mode (local vs API)** — On the **Node** installer (`npx @aporthq/aport-agent-guardrails …` / `bin/agent-guardrails`), every framework accepts the same flags: `--mode=api` (with optional `--api-url`, default `https://api.aport.io`) or `--mode=local`, and an optional hosted `ap_<hex>` argument (API mode, no local passport). That flow writes `…/aport/guardrail-mode.env` where the hooks/generic installers need it. The **Python** `aport setup` CLI does not parse those flags yet; use the Node command above for API/local mode during setup, or set mode in your framework `config.yaml` per the framework doc.
148
+
144
149
  **2. Hosted passport (optional)** — If you already have an agent_id from [aport.io](https://aport.io), use it to skip the wizard: `npx @aporthq/aport-agent-guardrails openclaw <agent_id>`. See [Hosted passport setup](docs/HOSTED_PASSPORT_SETUP.md).
145
150
 
146
151
  **3. Test that policy runs** — After setup, the guardrail runs automatically when your agent uses tools (Cursor hook, LangChain callback, OpenClaw plugin, etc.). To try allow/deny from the command line (any framework), use the installed `aport-guardrail` command (Node) or call the evaluator from Python; both use your existing passport from the framework config dir (e.g. `~/.cursor/aport/`, `~/.aport/langchain/aport/`).
@@ -8,11 +8,21 @@ set -e
8
8
 
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
11
- GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
12
11
 
13
12
  # Path resolver: probes ~/.claude, ~/.cursor, ~/.openclaw, etc.
14
13
  # shellcheck source=bin/aport-resolve-paths.sh
15
14
  . "$ROOT_DIR/bin/aport-resolve-paths.sh"
15
+ # shellcheck source=bin/lib/guardrail-mode.sh
16
+ . "$ROOT_DIR/bin/lib/guardrail-mode.sh"
17
+ load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.claude}"
18
+
19
+ GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
20
+ if [ "${APORT_GUARDRAIL_MODE:-local}" = "api" ]; then
21
+ GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-api.sh"
22
+ if [ -n "${APORT_API_URL:-}" ]; then
23
+ export APORT_API_URL
24
+ fi
25
+ fi
16
26
 
17
27
  # Read stdin
18
28
  INPUT=""
@@ -12,11 +12,21 @@ set -e
12
12
 
13
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
14
  ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
15
- GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
16
15
 
17
16
  # Passport/config: resolver probes ~/.cursor, ~/.openclaw, ~/.aport/*, etc.
18
17
  # shellcheck source=bin/aport-resolve-paths.sh
19
18
  . "$ROOT_DIR/bin/aport-resolve-paths.sh"
19
+ # shellcheck source=bin/lib/guardrail-mode.sh
20
+ . "$ROOT_DIR/bin/lib/guardrail-mode.sh"
21
+ load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}"
22
+
23
+ GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
24
+ if [ "${APORT_GUARDRAIL_MODE:-local}" = "api" ]; then
25
+ GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-api.sh"
26
+ if [ -n "${APORT_API_URL:-}" ]; then
27
+ export APORT_API_URL
28
+ fi
29
+ fi
20
30
 
21
31
  # Read stdin (single JSON object; Cursor sends one payload per invocation)
22
32
  INPUT=""
@@ -12,21 +12,43 @@ source "$LIB/passport.sh"
12
12
  source "$LIB/config.sh"
13
13
  # shellcheck source=../lib/framework-setup.sh
14
14
  source "$LIB/framework-setup.sh"
15
+ # shellcheck source=../lib/guardrail-mode.sh
16
+ source "$LIB/guardrail-mode.sh"
15
17
 
16
18
  run_setup() {
19
+ parse_guardrail_mode_args "$@"
20
+
17
21
  log_info "Setting up APort guardrails for Claude Code..."
18
22
  config_dir="$(ensure_aport_dir_secure claude-code)"
19
23
 
20
24
  export APORT_FRAMEWORK=claude-code
21
25
 
22
- # Check AGENTS.md for enforcement config — skip wizard if already configured
23
- # shellcheck source=../lib/agentsmd.sh
24
- source "$LIB/agentsmd.sh"
25
- setup_from_agentsmd_or_wizard "$@"
26
+ local hosted_agent_id=""
27
+ if [[ -n "${APORT_HOSTED_AGENT_ID_CLI:-}" ]]; then
28
+ hosted_agent_id="$APORT_HOSTED_AGENT_ID_CLI"
29
+ export APORT_AGENT_ID="$hosted_agent_id"
30
+ log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
31
+ else
32
+ # Check AGENTS.md for enforcement config — skip wizard if already configured
33
+ # shellcheck source=../lib/agentsmd.sh
34
+ source "$LIB/agentsmd.sh"
35
+ setup_from_agentsmd_or_wizard "${APORT_FRAMEWORK_ARGS[@]}"
36
+ fi
26
37
 
27
38
  # Harden permissions on passport (contains policy/capabilities)
28
39
  [ -f "$config_dir/aport/passport.json" ] && chmod 600 "$config_dir/aport/passport.json"
29
40
 
41
+ if [[ -z "$hosted_agent_id" && -n "${APORT_AGENT_ID:-}" ]]; then
42
+ hosted_agent_id="$APORT_AGENT_ID"
43
+ fi
44
+
45
+ select_guardrail_mode "claude-code" "$hosted_agent_id"
46
+ select_guardrail_api_url "$APORT_SELECTED_GUARDRAIL_MODE"
47
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
48
+ export APORT_API_URL="${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
49
+ fi
50
+ MODE_FILE="$(write_guardrail_mode_file "$config_dir" "$APORT_SELECTED_GUARDRAIL_MODE" "${APORT_SELECTED_API_URL:-}" "$hosted_agent_id")"
51
+
30
52
  # Resolve absolute path to hook script (works from repo or npx package)
31
53
  HOOK_SCRIPT="$(resolve_hook_script_path "${APORT_CLAUDE_CODE_HOOK_SCRIPT:-}" "aport-claude-code-hook.sh" "$LIB")"
32
54
  if [ ! -f "$HOOK_SCRIPT" ]; then
@@ -48,8 +70,13 @@ run_setup() {
48
70
  echo " ─────────────────────────"
49
71
  echo " 1. Settings written to: $SETTINGS_FILE"
50
72
  echo " 2. Hook script: $HOOK_SCRIPT"
51
- echo " 3. Restart Claude Code so the PreToolUse hook is picked up."
52
- echo " 4. Tool use will be checked by APort policy (exit 2 = block)."
73
+ echo " 3. Guardrail mode: $APORT_SELECTED_GUARDRAIL_MODE"
74
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
75
+ echo " API URL: ${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
76
+ fi
77
+ echo " 4. Mode config: $MODE_FILE"
78
+ echo " 5. Restart Claude Code so the PreToolUse hook is picked up."
79
+ echo " 6. Tool use will be checked by APort policy (exit 2 = block)."
53
80
  echo ""
54
81
  echo " Audit log: $config_dir/aport/audit.log"
55
82
  echo ""
@@ -12,22 +12,44 @@ source "$LIB/passport.sh"
12
12
  source "$LIB/config.sh"
13
13
  # shellcheck source=../lib/framework-setup.sh
14
14
  source "$LIB/framework-setup.sh"
15
+ # shellcheck source=../lib/guardrail-mode.sh
16
+ source "$LIB/guardrail-mode.sh"
15
17
 
16
18
  run_setup() {
19
+ parse_guardrail_mode_args "$@"
20
+
17
21
  log_info "Setting up APort guardrails for Cursor..."
18
22
  # Passport and data live under Cursor's config dir (~/.cursor/aport/ by default).
19
23
  config_dir="$(ensure_aport_dir_secure cursor)"
20
24
 
21
25
  export APORT_FRAMEWORK=cursor
22
26
 
23
- # Check AGENTS.md for enforcement config — skip wizard if already configured
24
- # shellcheck source=../lib/agentsmd.sh
25
- source "$LIB/agentsmd.sh"
26
- setup_from_agentsmd_or_wizard "$@"
27
+ local hosted_agent_id=""
28
+ if [[ -n "${APORT_HOSTED_AGENT_ID_CLI:-}" ]]; then
29
+ hosted_agent_id="$APORT_HOSTED_AGENT_ID_CLI"
30
+ export APORT_AGENT_ID="$hosted_agent_id"
31
+ log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
32
+ else
33
+ # Check AGENTS.md for enforcement config — skip wizard if already configured
34
+ # shellcheck source=../lib/agentsmd.sh
35
+ source "$LIB/agentsmd.sh"
36
+ setup_from_agentsmd_or_wizard "${APORT_FRAMEWORK_ARGS[@]}"
37
+ fi
27
38
 
28
39
  # Harden permissions on passport (contains policy/capabilities)
29
40
  [ -f "$config_dir/aport/passport.json" ] && chmod 600 "$config_dir/aport/passport.json"
30
41
 
42
+ if [[ -z "$hosted_agent_id" && -n "${APORT_AGENT_ID:-}" ]]; then
43
+ hosted_agent_id="$APORT_AGENT_ID"
44
+ fi
45
+
46
+ select_guardrail_mode "cursor" "$hosted_agent_id"
47
+ select_guardrail_api_url "$APORT_SELECTED_GUARDRAIL_MODE"
48
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
49
+ export APORT_API_URL="${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
50
+ fi
51
+ MODE_FILE="$(write_guardrail_mode_file "$config_dir" "$APORT_SELECTED_GUARDRAIL_MODE" "${APORT_SELECTED_API_URL:-}" "$hosted_agent_id")"
52
+
31
53
  # Resolve absolute path to hook script (works from repo or npx package)
32
54
  HOOK_SCRIPT="$(resolve_hook_script_path "${APORT_CURSOR_HOOK_SCRIPT:-}" "aport-cursor-hook.sh" "$LIB")"
33
55
  if [ ! -f "$HOOK_SCRIPT" ]; then
@@ -72,8 +94,13 @@ run_setup() {
72
94
  echo " ────────────────────"
73
95
  echo " 1. Hooks config written to: $CURSOR_HOOKS_FILE"
74
96
  echo " 2. Hook script: $HOOK_SCRIPT"
75
- echo " 3. Restart Cursor (or reload window) so hooks are picked up."
76
- echo " 4. Shell commands and tool use will be checked by APort policy (exit 2 = block)."
97
+ echo " 3. Guardrail mode: $APORT_SELECTED_GUARDRAIL_MODE"
98
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
99
+ echo " API URL: ${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
100
+ fi
101
+ echo " 4. Mode config: $MODE_FILE"
102
+ echo " 5. Restart Cursor (or reload window) so hooks are picked up."
103
+ echo " 6. Shell commands and tool use will be checked by APort policy (exit 2 = block)."
77
104
  echo ""
78
105
  echo " Same script works for VS Code + Copilot. For Claude Code use the dedicated integration: docs/frameworks/claude-code.md"
79
106
  echo ""
@@ -17,31 +17,48 @@ source "$LIB/passport.sh"
17
17
  source "$LIB/runtime.sh"
18
18
  # shellcheck source=../lib/config.sh
19
19
  source "$LIB/config.sh"
20
+ # shellcheck source=../lib/guardrail-mode.sh
21
+ source "$LIB/guardrail-mode.sh"
20
22
 
21
23
  framework="${APORT_FRAMEWORK:?APORT_FRAMEWORK must be set by the dispatcher}"
22
24
  crewai_integration_mode="${APORT_CREWAI_INTEGRATION_MODE:-compat}"
25
+ hosted_agent_id=""
23
26
 
27
+ parse_guardrail_mode_args "$@"
24
28
  FORWARD_ARGS=()
25
- while [[ $# -gt 0 ]]; do
26
- case "$1" in
29
+ if [[ -n "${APORT_FRAMEWORK_ARGS+x}" ]]; then
30
+ remaining_args=("${APORT_FRAMEWORK_ARGS[@]}")
31
+ else
32
+ remaining_args=()
33
+ fi
34
+ while [[ ${#remaining_args[@]} -gt 0 ]]; do
35
+ arg="${remaining_args[0]}"
36
+ remaining_args=("${remaining_args[@]:1}")
37
+ case "$arg" in
27
38
  --integration-mode=*)
28
- crewai_integration_mode="${1#--integration-mode=}"
39
+ crewai_integration_mode="${arg#--integration-mode=}"
29
40
  ;;
30
41
  --integration-mode)
31
- if [[ $# -lt 2 ]]; then
42
+ if [[ ${#remaining_args[@]} -lt 1 ]]; then
32
43
  log_error "--integration-mode requires compat or native"
33
44
  exit 1
34
45
  fi
35
- crewai_integration_mode="$2"
36
- shift
46
+ crewai_integration_mode="${remaining_args[0]}"
47
+ remaining_args=("${remaining_args[@]:1}")
48
+ ;;
49
+ ap_[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9])
50
+ hosted_agent_id="$arg"
37
51
  ;;
38
52
  *)
39
- FORWARD_ARGS+=("$1")
53
+ FORWARD_ARGS+=("$arg")
40
54
  ;;
41
55
  esac
42
- shift
43
56
  done
44
57
 
58
+ if [[ -n "${APORT_HOSTED_AGENT_ID_CLI:-}" ]]; then
59
+ hosted_agent_id="$APORT_HOSTED_AGENT_ID_CLI"
60
+ fi
61
+
45
62
  case "$crewai_integration_mode" in
46
63
  compat | native) ;;
47
64
  *)
@@ -65,6 +82,75 @@ resolve_next_steps_file() {
65
82
  echo "$FRAMEWORKS_DIR/next-steps.d/$framework.txt"
66
83
  }
67
84
 
85
+ _yaml_quote() {
86
+ local value="${1//\'/\'\'}"
87
+ printf "'%s'" "$value"
88
+ }
89
+
90
+ _config_replace_or_append() {
91
+ local file="$1"
92
+ local key="$2"
93
+ local value="$3"
94
+ local tmpfile
95
+
96
+ tmpfile="$(mktemp "${file}.XXXXXX")"
97
+ awk -v key="$key" -v value="$value" '
98
+ BEGIN { done = 0 }
99
+ $0 ~ "^[[:space:]]*" key ":" {
100
+ if (!done) {
101
+ print key ": " value
102
+ done = 1
103
+ }
104
+ next
105
+ }
106
+ { print }
107
+ END {
108
+ if (!done) {
109
+ print key ": " value
110
+ }
111
+ }
112
+ ' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
113
+ }
114
+
115
+ _config_remove_key() {
116
+ local file="$1"
117
+ local key="$2"
118
+ local tmpfile
119
+
120
+ tmpfile="$(mktemp "${file}.XXXXXX")"
121
+ awk -v key="$key" '$0 !~ "^[[:space:]]*" key ":" { print }' "$file" > "$tmpfile" && mv "$tmpfile" "$file"
122
+ }
123
+
124
+ persist_python_framework_config() {
125
+ local config_file="$1"
126
+ local selected_mode="$2"
127
+ local selected_api_url="$3"
128
+ local hosted_id="$4"
129
+ local local_passport_path="$5"
130
+
131
+ touch "$config_file"
132
+ _config_replace_or_append "$config_file" "framework" "$(_yaml_quote "$framework")"
133
+ _config_replace_or_append "$config_file" "mode" "$selected_mode"
134
+
135
+ if [[ "$selected_mode" == "api" ]]; then
136
+ _config_replace_or_append "$config_file" "api_url" "$(_yaml_quote "${selected_api_url:-$DEFAULT_APORT_API_URL}")"
137
+ if [[ -n "$hosted_id" ]]; then
138
+ _config_replace_or_append "$config_file" "agent_id" "$(_yaml_quote "$hosted_id")"
139
+ _config_remove_key "$config_file" "passport_path"
140
+ elif [[ -n "$local_passport_path" ]]; then
141
+ _config_replace_or_append "$config_file" "passport_path" "$(_yaml_quote "$local_passport_path")"
142
+ _config_remove_key "$config_file" "agent_id"
143
+ fi
144
+ else
145
+ if [[ -n "$local_passport_path" ]]; then
146
+ _config_replace_or_append "$config_file" "passport_path" "$(_yaml_quote "$local_passport_path")"
147
+ fi
148
+ _config_remove_key "$config_file" "agent_id"
149
+ fi
150
+
151
+ chmod 600 "$config_file" 2> /dev/null || true
152
+ }
153
+
68
154
  run_setup() {
69
155
  log_info "Setting up APort guardrails for $framework..."
70
156
  config_dir="$(write_config_template "$framework")"
@@ -72,10 +158,15 @@ run_setup() {
72
158
  chmod 700 "$config_dir/aport"
73
159
  install_runtime_tree "$config_dir"
74
160
  export APORT_FRAMEWORK="$framework"
161
+ if [[ -n "$hosted_agent_id" ]]; then
162
+ export APORT_AGENT_ID="$hosted_agent_id"
163
+ fi
75
164
  if [[ "$framework" == "crewai" ]]; then
76
165
  log_info "CrewAI integration mode: $crewai_integration_mode"
77
166
  fi
78
- if ((${#FORWARD_ARGS[@]} > 0)); then
167
+ if [[ -n "$hosted_agent_id" ]]; then
168
+ log_info "Using hosted passport (agent_id: $hosted_agent_id) — skipping wizard."
169
+ elif ((${#FORWARD_ARGS[@]} > 0)); then
79
170
  run_passport_wizard "${FORWARD_ARGS[@]}"
80
171
  else
81
172
  run_passport_wizard
@@ -83,6 +174,27 @@ run_setup() {
83
174
  # Harden permissions on passport (contains policy/capabilities)
84
175
  [ -f "$config_dir/aport/passport.json" ] && chmod 600 "$config_dir/aport/passport.json"
85
176
  log_info "Local runtime installed at: $config_dir/aport/runtime"
177
+ select_guardrail_mode "$framework" "$hosted_agent_id"
178
+ select_guardrail_api_url "$APORT_SELECTED_GUARDRAIL_MODE"
179
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
180
+ export APORT_API_URL="${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
181
+ fi
182
+ mode_file="$(write_guardrail_mode_file "$config_dir" "$APORT_SELECTED_GUARDRAIL_MODE" "${APORT_SELECTED_API_URL:-}" "$hosted_agent_id")"
183
+ local_passport_path=""
184
+ if [[ -f "$config_dir/aport/passport.json" ]]; then
185
+ local_passport_path="$config_dir/aport/passport.json"
186
+ fi
187
+ persist_python_framework_config \
188
+ "$config_dir/config.yaml" \
189
+ "$APORT_SELECTED_GUARDRAIL_MODE" \
190
+ "${APORT_SELECTED_API_URL:-}" \
191
+ "$hosted_agent_id" \
192
+ "$local_passport_path"
193
+ log_info "Guardrail mode: $APORT_SELECTED_GUARDRAIL_MODE"
194
+ if [[ "$APORT_SELECTED_GUARDRAIL_MODE" = "api" ]]; then
195
+ log_info "Guardrail API URL: ${APORT_SELECTED_API_URL:-$DEFAULT_APORT_API_URL}"
196
+ fi
197
+ log_info "Mode config: $mode_file"
86
198
 
87
199
  # Print framework-specific next steps from data file
88
200
  next_steps="$(resolve_next_steps_file)"
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bash
2
+ # Shared guardrail mode helpers for hook-based integrations (Cursor/Claude Code).
3
+ # Stores runtime mode under <config_dir>/aport/guardrail-mode.env so hooks can
4
+ # select local vs API evaluator consistently across sessions.
5
+
6
+ DEFAULT_APORT_API_URL="${DEFAULT_APORT_API_URL:-https://api.aport.io}"
7
+
8
+ parse_guardrail_mode_args() {
9
+ APORT_GUARDRAIL_MODE_CLI="${APORT_GUARDRAIL_MODE_CLI:-}"
10
+ APORT_GUARDRAIL_API_URL_CLI="${APORT_GUARDRAIL_API_URL_CLI:-}"
11
+ APORT_HOSTED_AGENT_ID_CLI="${APORT_HOSTED_AGENT_ID_CLI:-}"
12
+ APORT_FRAMEWORK_ARGS=()
13
+
14
+ while [[ $# -gt 0 ]]; do
15
+ case "$1" in
16
+ --mode=*)
17
+ APORT_GUARDRAIL_MODE_CLI="${1#--mode=}"
18
+ ;;
19
+ --mode)
20
+ if [[ -z "${2:-}" ]]; then
21
+ echo "[aport] ERROR: --mode requires a value (local|api)" >&2
22
+ return 1
23
+ fi
24
+ APORT_GUARDRAIL_MODE_CLI="$2"
25
+ shift
26
+ ;;
27
+ --api-url=*)
28
+ APORT_GUARDRAIL_API_URL_CLI="${1#--api-url=}"
29
+ ;;
30
+ --api-url)
31
+ if [[ -z "${2:-}" ]]; then
32
+ echo "[aport] ERROR: --api-url requires a value" >&2
33
+ return 1
34
+ fi
35
+ APORT_GUARDRAIL_API_URL_CLI="$2"
36
+ shift
37
+ ;;
38
+ ap_[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9])
39
+ APORT_HOSTED_AGENT_ID_CLI="$1"
40
+ ;;
41
+ *)
42
+ APORT_FRAMEWORK_ARGS+=("$1")
43
+ ;;
44
+ esac
45
+ shift
46
+ done
47
+
48
+ export APORT_GUARDRAIL_MODE_CLI APORT_GUARDRAIL_API_URL_CLI APORT_HOSTED_AGENT_ID_CLI
49
+ return 0
50
+ }
51
+
52
+ select_guardrail_mode() {
53
+ local framework="$1"
54
+ local hosted_agent_id="${2:-}"
55
+ local noninteractive="${APORT_NONINTERACTIVE:-${CI:-}}"
56
+ local selected_mode="${APORT_GUARDRAIL_MODE_CLI:-${APORT_GUARDRAIL_MODE:-}}"
57
+
58
+ if [[ -n "$hosted_agent_id" ]]; then
59
+ APORT_SELECTED_GUARDRAIL_MODE="api"
60
+ export APORT_SELECTED_GUARDRAIL_MODE
61
+ return 0
62
+ fi
63
+
64
+ if [[ -n "$selected_mode" ]]; then
65
+ selected_mode="$(echo "$selected_mode" | tr '[:upper:]' '[:lower:]')"
66
+ case "$selected_mode" in
67
+ local | api) ;;
68
+ *)
69
+ echo "[aport] ERROR: Unsupported --mode value: $selected_mode (expected local|api)" >&2
70
+ return 1
71
+ ;;
72
+ esac
73
+ APORT_SELECTED_GUARDRAIL_MODE="$selected_mode"
74
+ export APORT_SELECTED_GUARDRAIL_MODE
75
+ return 0
76
+ fi
77
+
78
+ if [[ -n "$noninteractive" ]]; then
79
+ # Keep non-interactive deterministic and offline-friendly unless explicitly overridden.
80
+ APORT_SELECTED_GUARDRAIL_MODE="local"
81
+ export APORT_SELECTED_GUARDRAIL_MODE
82
+ return 0
83
+ fi
84
+
85
+ echo ""
86
+ echo " Guardrail mode:"
87
+ echo " 1. local - Use local evaluator (offline, no network)"
88
+ echo " 2. api - Use APort API evaluator"
89
+ echo ""
90
+ read -r -p " Mode [1=local, 2=api]: " mode_choice
91
+ mode_choice="${mode_choice:-2}"
92
+ if [[ "$mode_choice" = "2" ]]; then
93
+ APORT_SELECTED_GUARDRAIL_MODE="api"
94
+ else
95
+ APORT_SELECTED_GUARDRAIL_MODE="local"
96
+ fi
97
+ export APORT_SELECTED_GUARDRAIL_MODE
98
+ return 0
99
+ }
100
+
101
+ select_guardrail_api_url() {
102
+ local mode="$1"
103
+ local noninteractive="${APORT_NONINTERACTIVE:-${CI:-}}"
104
+ local configured_url="${APORT_GUARDRAIL_API_URL_CLI:-${APORT_API_URL:-$DEFAULT_APORT_API_URL}}"
105
+
106
+ APORT_SELECTED_API_URL=""
107
+ if [[ "$mode" != "api" ]]; then
108
+ export APORT_SELECTED_API_URL
109
+ return 0
110
+ fi
111
+
112
+ if [[ -n "$noninteractive" ]]; then
113
+ APORT_SELECTED_API_URL="$configured_url"
114
+ export APORT_SELECTED_API_URL
115
+ return 0
116
+ fi
117
+
118
+ read -r -p " APort API URL [$configured_url]: " api_url_input
119
+ APORT_SELECTED_API_URL="${api_url_input:-$configured_url}"
120
+ export APORT_SELECTED_API_URL
121
+ return 0
122
+ }
123
+
124
+ write_guardrail_mode_file() {
125
+ local config_dir="$1"
126
+ local mode="$2"
127
+ local api_url="$3"
128
+ local hosted_agent_id="${4:-}"
129
+
130
+ local aport_dir="$config_dir/aport"
131
+ local mode_file="$aport_dir/guardrail-mode.env"
132
+ mkdir -p "$aport_dir"
133
+
134
+ {
135
+ echo "APORT_GUARDRAIL_MODE=$mode"
136
+ if [[ "$mode" = "api" ]]; then
137
+ echo "APORT_API_URL=${api_url:-$DEFAULT_APORT_API_URL}"
138
+ fi
139
+ if [[ -n "$hosted_agent_id" ]]; then
140
+ echo "APORT_AGENT_ID=$hosted_agent_id"
141
+ fi
142
+ } > "$mode_file"
143
+ chmod 600 "$mode_file" 2> /dev/null || true
144
+ echo "$mode_file"
145
+ }
146
+
147
+ load_guardrail_mode_for_hooks() {
148
+ local config_dir="$1"
149
+ local mode_file="$config_dir/aport/guardrail-mode.env"
150
+ if [[ -f "$mode_file" ]]; then
151
+ # shellcheck disable=SC1090
152
+ source "$mode_file"
153
+ fi
154
+ }