@aporthq/aport-agent-guardrails 1.0.25 → 1.0.27

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
@@ -55,6 +55,7 @@ npx @aporthq/aport-agent-guardrails
55
55
  - Choose your framework: `openclaw`, `cursor`, `claude-code`, `langchain`, `crewai`, `deerflow`, `n8n`
56
56
  - OpenClaw direct: `npx @aporthq/aport-agent-guardrails openclaw`
57
57
  - Hosted passport: `npx @aporthq/aport-agent-guardrails openclaw <agent_id>`
58
+ - Reset a framework to a clean APort state: `npx @aporthq/aport-agent-guardrails reset claude-code --yes`
58
59
 
59
60
  ### Why Developers and teams trust APort
60
61
 
@@ -129,6 +130,22 @@ npx @aporthq/aport-agent-guardrails
129
130
  # --mode=local
130
131
  ```
131
132
 
133
+ **Reset / uninstall APort-owned wiring**
134
+
135
+ Use the same dispatcher for cleanup:
136
+
137
+ ```bash
138
+ npx @aporthq/aport-agent-guardrails reset claude-code --yes
139
+ # or
140
+ npx @aporthq/aport-agent-guardrails claude-code reset --yes
141
+ ```
142
+
143
+ Supported reset targets match the CLI-supported frameworks:
144
+ `openclaw`, `cursor`, `claude-code`, `langchain`, `crewai`, `deerflow`, `n8n`.
145
+
146
+ Reset removes APort-owned config and integration wiring for the selected framework.
147
+ When possible, unrelated user hooks are preserved.
148
+
132
149
  **Python (LangChain, CrewAI, or DeerFlow):** Use the Python CLI directly via `uvx` or an installed package:
133
150
  ```bash
134
151
  uvx --from aport-agent-guardrails aport setup --framework=langchain
@@ -183,7 +200,7 @@ Your framework doc (Cursor, OpenClaw, LangChain, CrewAI) describes where the con
183
200
  | **Bypass risk** | None | High |
184
201
  | **Recommended** | **Yes** | Only if plugin unavailable |
185
202
 
186
- **Plugin (recommended):** Platform runs the guardrail before every tool; the model cannot skip it. This repo implements the **plugin (before_tool_call)** integration—Option 2 in the [APort × OpenClaw integration proposal](https://github.com/aporthq/agent-passport/tree/main/_plan/execution/openclaw).
203
+ **Plugin (recommended):** Platform runs the guardrail before every tool; the model cannot skip it. This repo implements the public **plugin (before_tool_call)** integration for OpenClaw.
187
204
  **AGENTS.md:** Agent is *instructed* to call the guardrail; best-effort only.
188
205
 
189
206
  ---
@@ -405,6 +422,7 @@ See [Verification methods](docs/VERIFICATION_METHODS.md) for a detailed comparis
405
422
  | Command | Purpose |
406
423
  |--------|---------|
407
424
  | `agent-guardrails` | Main entry — prompt for framework or pass one: `agent-guardrails openclaw \| cursor \| claude-code \| langchain \| crewai \| deerflow \| n8n`. Args after the framework are passed through (e.g. `agent-guardrails openclaw <agent_id>`). |
425
+ | `agent-guardrails reset <framework> [--yes]` | Remove APort-owned config and hook/plugin wiring for one framework. Positional form also works: `agent-guardrails <framework> reset --yes`. |
408
426
  | `aport` | OpenClaw one-command setup (passport + plugin + wrappers). Optional: `aport <agent_id>` for hosted passport. |
409
427
  | `aport-guardrail` | Run guardrail check from the CLI (e.g. `aport-guardrail system.command.execute '{"command":"ls"}'`). Uses passport from your framework config dir. |
410
428
 
@@ -517,7 +535,7 @@ Contributions welcome: policy packs, framework adapters, docs. See [CONTRIBUTING
517
535
 
518
536
  Apache 2.0 — see [LICENSE](LICENSE).
519
537
 
520
- **Open-core:** Local evaluation and CLI in this repo are open source (Apache 2.0). [api.aport.io](https://api.aport.io) is a separate product for cloud features (signed receipts, global kill switch, team sync). See [APort × OpenClaw proposal](https://github.com/aporthq/agent-passport/tree/main/_plan/execution/openclaw) for free vs. paid tiers.
538
+ **Open-core:** Local evaluation and CLI in this repo are open source (Apache 2.0). [api.aport.io](https://api.aport.io) is a separate product for cloud features such as signed receipts, global kill switch, and team sync.
521
539
 
522
540
  ---
523
541
 
@@ -527,5 +545,3 @@ Apache 2.0 — see [LICENSE](LICENSE).
527
545
  - [GitHub Issues](https://github.com/aporthq/aport-agent-guardrails/issues) · [Discussions](https://github.com/aporthq/aport-agent-guardrails/discussions)
528
546
 
529
547
  ---
530
-
531
- <p align="center">Made with ❤️ by [Uchi](https://github.com/uchibeke/) </p>
@@ -29,6 +29,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
29
29
  FRAMEWORKS_DIR="$SCRIPT_DIR/frameworks"
30
30
  LIB_DIR="$SCRIPT_DIR/lib"
31
31
  SUPPORTED_FRAMEWORKS=(openclaw langchain crewai cursor claude-code deerflow n8n)
32
+ reset_requested=""
32
33
 
33
34
  framework_supported() {
34
35
  local candidate="${1:-}"
@@ -44,6 +45,7 @@ framework_supported() {
44
45
  # Parse --framework= and -f (skip detection when set)
45
46
  framework=""
46
47
  integration_mode=""
48
+ noninteractive_requested=""
47
49
  REST=()
48
50
  while [[ $# -gt 0 ]]; do
49
51
  case "$1" in
@@ -73,6 +75,10 @@ while [[ $# -gt 0 ]]; do
73
75
  exit 1
74
76
  fi
75
77
  ;;
78
+ --non-interactive|--noninteractive)
79
+ noninteractive_requested="1"
80
+ shift
81
+ ;;
76
82
  *)
77
83
  REST+=("$1")
78
84
  shift
@@ -80,7 +86,23 @@ while [[ $# -gt 0 ]]; do
80
86
  esac
81
87
  done
82
88
 
89
+ if [[ -n "$noninteractive_requested" ]]; then
90
+ export APORT_NONINTERACTIVE=1
91
+ fi
92
+
83
93
  # If no framework from args, check if first REST argument is a framework name
94
+ if [[ -z "$framework" ]] && [[ ${#REST[@]} -gt 0 ]]; then
95
+ first_arg="${REST[0]}"
96
+ if [[ "$first_arg" == "reset" ]] && [[ ${#REST[@]} -gt 1 ]]; then
97
+ second_arg="${REST[1]}"
98
+ if framework_supported "$second_arg"; then
99
+ framework="$second_arg"
100
+ reset_requested="1"
101
+ REST=("${REST[@]:2}")
102
+ fi
103
+ fi
104
+ fi
105
+
84
106
  if [[ -z "$framework" ]] && [[ ${#REST[@]} -gt 0 ]]; then
85
107
  first_arg="${REST[0]}"
86
108
  # Check if first arg looks like a framework name (lowercase alphanumeric + hyphen)
@@ -93,6 +115,11 @@ if [[ -z "$framework" ]] && [[ ${#REST[@]} -gt 0 ]]; then
93
115
  fi
94
116
  fi
95
117
 
118
+ if [[ -n "$framework" ]] && [[ ${#REST[@]} -gt 0 ]] && [[ "${REST[0]}" == "reset" ]]; then
119
+ reset_requested="1"
120
+ REST=("${REST[@]:1}")
121
+ fi
122
+
96
123
  # If still no framework from args, try APORT_FRAMEWORK (non-interactive) or detection
97
124
  if [[ -z "$framework" ]]; then
98
125
  if [[ -n "${APORT_FRAMEWORK:-}" ]]; then
@@ -165,6 +192,15 @@ fi
165
192
 
166
193
  echo "[aport] Selected framework: $framework" >&2
167
194
 
195
+ if [[ -n "$reset_requested" ]]; then
196
+ reset_script="$SCRIPT_DIR/aport-reset-framework.sh"
197
+ if [[ ! -x "$reset_script" ]]; then
198
+ echo "[aport] ERROR: Reset helper not found: $reset_script" >&2
199
+ exit 1
200
+ fi
201
+ exec "$reset_script" "$framework" ${REST+"${REST[@]}"}
202
+ fi
203
+
168
204
  if [[ -n "$integration_mode" ]]; then
169
205
  case "$integration_mode" in
170
206
  compat|native) ;;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # APort Claude Code hook: reads tool_name + tool_input from JSON stdin,
2
+ # APort Claude Code hook: reads tool_name + tool_input from JSON stdin (path-based Read uses guardrail).
3
3
  # maps to APort policy, calls guardrail, outputs hookSpecificOutput deny or exit 0.
4
4
  # Exit 0 = allow, exit 2 = block. Other exits = hook error (Claude Code may fail-open).
5
5
  # Output format: Claude Code official schema (hookSpecificOutput.permissionDecision), NOT Cursor format.
@@ -14,6 +14,8 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
14
14
  . "$ROOT_DIR/bin/aport-resolve-paths.sh"
15
15
  # shellcheck source=bin/lib/guardrail-mode.sh
16
16
  . "$ROOT_DIR/bin/lib/guardrail-mode.sh"
17
+ # shellcheck source=bin/lib/hook-read-policy.sh
18
+ . "$ROOT_DIR/bin/lib/hook-read-policy.sh"
17
19
  load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.claude}"
18
20
 
19
21
  GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
@@ -49,7 +51,8 @@ set -e
49
51
  if [ "$JQ_EXIT" -ne 0 ] || [ -z "$TOOL_NAME" ]; then
50
52
  TOOL_NAME="unknown"
51
53
  fi
52
- TOOL_NAME_NORM="$(printf '%s' "$TOOL_NAME" | tr -d '[:space:]' | sed 's/^functions\.//' | tr '[:upper:]' '[:lower:]')"
54
+ # Strip permission-rule specifiers (e.g. Agent(Explore) -> Agent) before normalization.
55
+ TOOL_NAME_NORM="$(printf '%s' "$TOOL_NAME" | tr -d '[:space:]' | sed 's/^functions\.//' | sed 's/(.*$//' | tr '[:upper:]' '[:lower:]')"
53
56
  set +e
54
57
  TOOL_INPUT="$(echo "$INPUT" | jq -c '.tool_input // {}' 2> /dev/null)"
55
58
  JQ_EXIT=$?
@@ -81,40 +84,45 @@ GUARDRAIL_TOOL=""
81
84
  CONTEXT_JSON="{}"
82
85
 
83
86
  case "$TOOL_NAME_NORM" in
84
- bash | shell)
87
+ bash | shell | powershell | monitor)
85
88
  GUARDRAIL_TOOL="bash"
86
- CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{command: (.command // "")}')"
89
+ CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{command: (.command // .script // "")}')"
87
90
  ;;
88
- read | glob | ls | grep | todoread | toolsearch | askuserquestion | readfile | semanticsearch)
89
- # Read-family + user-interaction tools: allow without calling evaluator
91
+ read | readfile | semanticsearch)
92
+ if ! aport_hook_try_read_evaluation "$TOOL_NAME_NORM" "$TOOL_INPUT"; then
93
+ exit 0
94
+ fi
95
+ ;;
96
+ glob | ls | grep | lsp | todoread | toolsearch | askuserquestion | listmcpresourcestool | readmcpresourcetool | waitformcpservers)
97
+ # Search/list/read tools without a single file_path: allow without evaluator
90
98
  exit 0
91
99
  ;;
92
- taskget | tasklist | taskoutput | cronlist)
93
- # Read-only task/cron queries: allow without evaluator
100
+ taskget | tasklist | taskoutput | cronlist | schedulewakeup | pushnotification)
101
+ # Read-only task/cron queries and notifications: allow without evaluator
94
102
  exit 0
95
103
  ;;
96
104
  enterplanmode | exitplanmode)
97
105
  # Internal state transitions: allow without evaluator
98
106
  exit 0
99
107
  ;;
100
- write | edit | multiedit | notebookedit | todowrite | delete | strreplace | editnotebook)
108
+ write | edit | multiedit | notebookedit | todowrite | delete | strreplace | editnotebook | shareonboardingguide)
101
109
  GUARDRAIL_TOOL="write"
102
110
  CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{file_path: (.file_path // .path // "")}')"
103
111
  ;;
104
112
  websearch | webfetch)
105
- GUARDRAIL_TOOL="webfetch"
106
- CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{url: (.url // .query // "")}')"
113
+ GUARDRAIL_TOOL="websearch"
114
+ CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{url: (.url // ""), query: (.query // "")}')"
107
115
  ;;
108
116
  browser)
109
117
  GUARDRAIL_TOOL="browser"
110
118
  CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{url: (.url // "")}')"
111
119
  ;;
112
- task | taskcreate | taskupdate | taskstop | agent | skill | enterworktree | subagent | subagentstart)
120
+ agent | task | taskcreate | taskupdate | taskstop | skill | enterworktree | exitworktree | subagent | subagentstart | sendmessage | teamcreate | teamdelete | remotetrigger)
113
121
  GUARDRAIL_TOOL="session.create"
114
- CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{description: (.description // .prompt // "")}')"
122
+ CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{description: (.description // .prompt // .task // .message // ""), subagent_type: (.subagent_type // .agent_type // "")}')"
115
123
  ;;
116
124
  croncreate | crondelete)
117
- GUARDRAIL_TOOL="cron"
125
+ GUARDRAIL_TOOL="session.create"
118
126
  CONTEXT_JSON="$(safe_jq "$TOOL_INPUT" '{description: (.description // .schedule // "")}')"
119
127
  ;;
120
128
  mcp__* | mcp:* | callmcptool)
@@ -134,6 +142,14 @@ if [ -n "$HOOK_DECISION_FILE" ]; then
134
142
  export OPENCLAW_DECISION_FILE="$HOOK_DECISION_FILE"
135
143
  fi
136
144
 
145
+ # Read tools: send only file_path to the evaluator (Claude may attach large file bodies in tool_input).
146
+ if [ "$GUARDRAIL_TOOL" = "read" ]; then
147
+ CONTEXT_JSON="$(printf '%s' "$CONTEXT_JSON" | jq -c '{file_path: (.file_path // .path // "")}' 2> /dev/null || echo '{"file_path":""}')"
148
+ if [ -z "$(printf '%s' "$CONTEXT_JSON" | jq -r '.file_path // ""' 2> /dev/null)" ]; then
149
+ exit 0
150
+ fi
151
+ fi
152
+
137
153
  # Call core evaluator (guardrail expects tool name, not policy ID)
138
154
  set +e
139
155
  "$GUARDRAIL" "$GUARDRAIL_TOOL" "$CONTEXT_JSON" 2> /dev/null
@@ -18,6 +18,8 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
18
18
  . "$ROOT_DIR/bin/aport-resolve-paths.sh"
19
19
  # shellcheck source=bin/lib/guardrail-mode.sh
20
20
  . "$ROOT_DIR/bin/lib/guardrail-mode.sh"
21
+ # shellcheck source=bin/lib/hook-read-policy.sh
22
+ . "$ROOT_DIR/bin/lib/hook-read-policy.sh"
21
23
  load_guardrail_mode_for_hooks "${OPENCLAW_CONFIG_DIR:-$HOME/.cursor}"
22
24
 
23
25
  GUARDRAIL="$ROOT_DIR/bin/aport-guardrail-bash.sh"
@@ -82,8 +84,10 @@ HOOK_EVENT="$(echo "$INPUT" | jq -r '.hook_event_name // ""' 2> /dev/null)"
82
84
  TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // ""' 2> /dev/null)"
83
85
 
84
86
  if [ "$HOOK_EVENT" = "beforeReadFile" ] || { [ -z "$HOOK_EVENT" ] && [ -z "$TOOL_NAME" ] && echo "$INPUT" | jq -e '.file_path and .content' &> /dev/null; }; then
85
- # beforeReadFile: file reads allow without evaluator (same as Claude Code)
86
- exit 0
87
+ FILE_PATH="$(echo "$INPUT" | jq -r '.file_path // ""' 2> /dev/null || true)"
88
+ if ! aport_hook_try_read_evaluation_from_file_path "$FILE_PATH"; then
89
+ exit 0
90
+ fi
87
91
 
88
92
  elif [ "$HOOK_EVENT" = "subagentStart" ] || { [ -z "$HOOK_EVENT" ] && echo "$INPUT" | jq -e '.subagent_id' &> /dev/null; }; then
89
93
  # subagentStart: sub-agent spawning
@@ -96,36 +100,48 @@ elif [ "$HOOK_EVENT" = "beforeMCPExecution" ] || { [ -n "$TOOL_NAME" ] && echo "
96
100
  CONTEXT_JSON="$(safe_jq "$INPUT" '{tool_name: (.tool_name // ""), tool_input: (.tool_input // {})}')"
97
101
 
98
102
  elif [ -n "$TOOL_NAME" ]; then
99
- # preToolUse: Cursor tool names — Shell, Read, Write, Grep, Delete, Task, MCP:<name>
100
- # Normalize: trim whitespace, lowercase (patterns must match TOOL_NORM)
101
- TOOL_NORM="$(echo "$TOOL_NAME" | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')"
103
+ # preToolUse: Shell, Read, Write, Grep, Delete, Task, WebSearch, Agent, MCP:*, etc.
104
+ # See docs/FRAMEWORK_TOOL_MAPPING_AUDIT.md and https://cursor.com/docs/agent/hooks
105
+ TOOL_NORM="$(printf '%s' "$TOOL_NAME" | tr -d '[:space:]' | sed 's/^functions\.//' | sed 's/(.*$//' | tr '[:upper:]' '[:lower:]')"
102
106
  case "$TOOL_NORM" in
103
- shell)
107
+ shell | bash)
104
108
  GUARDRAIL_TOOL="bash"
105
109
  CONTEXT_JSON="$(safe_jq "$INPUT" '{command: (.tool_input.command // "")}')"
106
110
  ;;
107
- read | grep | glob | semanticsearch)
108
- # Read-family: allow without calling evaluator (matches Claude Code behavior)
111
+ read | readfile | semanticsearch)
112
+ TOOL_INPUT="$(safe_jq "$INPUT" '.tool_input // {}')"
113
+ if ! aport_hook_try_read_evaluation "$TOOL_NORM" "$TOOL_INPUT"; then
114
+ exit 0
115
+ fi
116
+ ;;
117
+ grep | glob | ls | lsp | listmcpresourcestool | readmcpresourcetool | toolsearch | waitformcpservers | taskget | tasklist | taskoutput | cronlist)
109
118
  exit 0
110
119
  ;;
111
- write | strreplace | editnotebook)
120
+ write | strreplace | edit | multiedit | editnotebook | applypatch | notebookedit | delete)
112
121
  GUARDRAIL_TOOL="write"
113
122
  CONTEXT_JSON="$(safe_jq "$INPUT" '{file_path: (.tool_input.file_path // .tool_input.path // "")}')"
114
123
  ;;
115
- delete)
116
- GUARDRAIL_TOOL="write"
117
- CONTEXT_JSON="$(safe_jq "$INPUT" '{file_path: (.tool_input.file_path // .tool_input.path // "")}')"
124
+ websearch | webfetch)
125
+ GUARDRAIL_TOOL="websearch"
126
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{url: (.tool_input.url // ""), query: (.tool_input.query // "")}')"
127
+ ;;
128
+ browser)
129
+ GUARDRAIL_TOOL="browser"
130
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{url: (.tool_input.url // "")}')"
118
131
  ;;
119
- task)
132
+ task | agent | taskcreate | taskupdate | taskstop | skill | subagent | subagentstart | sendmessage | teamcreate | teamdelete)
120
133
  GUARDRAIL_TOOL="session.create"
121
134
  CONTEXT_JSON="$(safe_jq "$INPUT" '{description: (.tool_input.description // .tool_input.prompt // "")}')"
122
135
  ;;
123
- mcp:*)
136
+ croncreate | crondelete)
137
+ GUARDRAIL_TOOL="session.create"
138
+ CONTEXT_JSON="$(safe_jq "$INPUT" '{description: (.tool_input.description // .tool_input.schedule // "")}')"
139
+ ;;
140
+ mcp__* | mcp:* | callmcptool)
124
141
  GUARDRAIL_TOOL="mcp.tool"
125
142
  CONTEXT_JSON="$(safe_jq "$INPUT" '{tool_name: (.tool_name // ""), tool_input: (.tool_input // {})}')"
126
143
  ;;
127
144
  *)
128
- # Unknown preToolUse tool: fail-closed
129
145
  deny "🛡️ APort: unknown tool '$TOOL_NAME' — fail-closed policy"
130
146
  ;;
131
147
  esac
@@ -154,6 +170,15 @@ if [ -n "$HOOK_DECISION_FILE" ]; then
154
170
  export OPENCLAW_DECISION_FILE="$HOOK_DECISION_FILE"
155
171
  fi
156
172
 
173
+ # Read tools: send only file_path to the evaluator (Cursor may attach large file bodies in tool_input).
174
+ if [ "$GUARDRAIL_TOOL" = "read" ]; then
175
+ CONTEXT_JSON="$(printf '%s' "$CONTEXT_JSON" | jq -c '{file_path: (.file_path // .path // "")}' 2> /dev/null || echo '{"file_path":""}')"
176
+ if [ -z "$(printf '%s' "$CONTEXT_JSON" | jq -r '.file_path // ""' 2> /dev/null)" ]; then
177
+ echo '{"permission":"allow","allowed":true}'
178
+ exit 0
179
+ fi
180
+ fi
181
+
157
182
  # Call core evaluator
158
183
  set +e
159
184
  "$GUARDRAIL" "$GUARDRAIL_TOOL" "$CONTEXT_JSON" 2> /dev/null
@@ -301,6 +301,17 @@ else
301
301
  LIMITS=$(echo "$PASSPORT" | jq ".limits.\"$POLICY_BASE\" // {}")
302
302
  fi
303
303
 
304
+ is_default_sensitive_read_path() {
305
+ local path_lower
306
+ path_lower="$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')"
307
+ case "$path_lower" in
308
+ .env* | */.env* | .aws/* | */.aws/* | .ssh/* | */.ssh/* | *credentials* | *id_rsa* | *id_dsa* | *id_ecdsa* | *id_ed25519* | *.pem | *.key | *password* | .gnupg/* | */.gnupg/* | .kube/* | */.kube/*)
309
+ return 0
310
+ ;;
311
+ esac
312
+ return 1
313
+ }
314
+
304
315
  # Evaluate policy-specific limits
305
316
  if [[ "$POLICY_ID" == "code.repository.merge"* ]]; then
306
317
  FILES_CHANGED=$(echo "$CONTEXT_JSON" | jq -r '.files_changed // .files // 0')
@@ -426,6 +437,10 @@ fi
426
437
  if [[ "$POLICY_ID" == "data.file.read.v1" ]]; then
427
438
  FILE_PATH=$(echo "$CONTEXT_JSON" | jq -r '.file_path // .path // ""')
428
439
  if [ -n "$FILE_PATH" ]; then
440
+ if is_default_sensitive_read_path "$FILE_PATH"; then
441
+ write_decision false "$POLICY_ID" "oap.blocked_pattern" "File path matches default sensitive read pattern"
442
+ fi
443
+
429
444
  # Check allowed paths
430
445
  PATH_ALLOWED=false
431
446
  while IFS= read -r allowed_path; do
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ LIB="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")/lib" && pwd)"
6
+ # shellcheck source=./lib/common.sh
7
+ source "$LIB/common.sh"
8
+ # shellcheck source=./lib/config.sh
9
+ source "$LIB/config.sh"
10
+
11
+ framework="${1:-}"
12
+ shift || true
13
+
14
+ if [[ -z "$framework" ]]; then
15
+ log_error "Usage: agent-guardrails reset <framework> [--yes]"
16
+ exit 1
17
+ fi
18
+
19
+ yes_mode="${APORT_NONINTERACTIVE:-${CI:-}}"
20
+ while [[ $# -gt 0 ]]; do
21
+ case "$1" in
22
+ --yes | -y)
23
+ yes_mode=1
24
+ ;;
25
+ *)
26
+ log_error "Unknown reset option: $1"
27
+ exit 1
28
+ ;;
29
+ esac
30
+ shift
31
+ done
32
+
33
+ framework="$(echo "$framework" | tr '[:upper:]' '[:lower:]')"
34
+ config_dir="$(get_config_dir "$framework")"
35
+ config_dir="${config_dir/#\~/$HOME}"
36
+
37
+ confirm_reset() {
38
+ if [[ -n "$yes_mode" ]]; then
39
+ return 0
40
+ fi
41
+
42
+ echo ""
43
+ echo " Reset APort for $framework"
44
+ echo " ──────────────────────────"
45
+ echo " This removes APort-owned local config and hook/plugin wiring for this framework."
46
+ echo " Other framework settings are preserved when possible."
47
+ echo ""
48
+
49
+ local answer
50
+ read -r -p " Continue? [y/N]: " answer
51
+ case "$answer" in
52
+ y | Y | yes | YES) ;;
53
+ *)
54
+ echo " Reset cancelled."
55
+ exit 0
56
+ ;;
57
+ esac
58
+ }
59
+
60
+ backup_file() {
61
+ local file="$1"
62
+ if [[ -f "$file" ]]; then
63
+ cp "$file" "${file}.bak"
64
+ fi
65
+ }
66
+
67
+ remove_dir_if_exists() {
68
+ local dir="$1"
69
+ if [[ -d "$dir" ]]; then
70
+ rm -rf "$dir"
71
+ echo " ✅ Removed $dir"
72
+ fi
73
+ }
74
+
75
+ remove_file_if_exists() {
76
+ local file="$1"
77
+ if [[ -f "$file" ]]; then
78
+ rm -f "$file"
79
+ echo " ✅ Removed $file"
80
+ fi
81
+ }
82
+
83
+ cleanup_claude_settings() {
84
+ local settings_file="$1"
85
+ if [[ ! -f "$settings_file" ]]; then
86
+ return 0
87
+ fi
88
+ if ! command -v jq &> /dev/null; then
89
+ log_warn "jq not found; leaving Claude settings intact at $settings_file"
90
+ return 0
91
+ fi
92
+
93
+ local tmpfile
94
+ tmpfile="$(mktemp "${settings_file}.XXXXXX")"
95
+ if jq '
96
+ if (.hooks.PreToolUse? // null) == null then
97
+ .
98
+ else
99
+ .hooks.PreToolUse = (
100
+ (.hooks.PreToolUse // [])
101
+ | map(
102
+ if (.hooks? // null) == null then
103
+ .
104
+ else
105
+ .hooks = (
106
+ (.hooks // [])
107
+ | map(select((((.command // "") | test("aport-(cursor-hook|claude-code-hook)\\.sh$")) | not)))
108
+ )
109
+ end
110
+ )
111
+ | map(select(((.hooks? // []) | length) > 0))
112
+ )
113
+ | if ((.hooks.PreToolUse // []) | length) == 0 then del(.hooks.PreToolUse) else . end
114
+ | if ((.hooks // {}) | keys | length) == 0 then del(.hooks) else . end
115
+ end
116
+ ' "$settings_file" > "$tmpfile"; then
117
+ backup_file "$settings_file"
118
+ mv "$tmpfile" "$settings_file"
119
+ echo " ✅ Removed APort Claude Code hook entries from $settings_file"
120
+ else
121
+ rm -f "$tmpfile"
122
+ log_warn "Failed to clean Claude settings at $settings_file"
123
+ fi
124
+ }
125
+
126
+ cleanup_cursor_hooks() {
127
+ local hooks_file="$1"
128
+ if [[ ! -f "$hooks_file" ]]; then
129
+ return 0
130
+ fi
131
+ if ! command -v jq &> /dev/null; then
132
+ log_warn "jq not found; leaving Cursor hooks intact at $hooks_file"
133
+ return 0
134
+ fi
135
+
136
+ local tmpfile
137
+ tmpfile="$(mktemp "${hooks_file}.XXXXXX")"
138
+ if jq '
139
+ def strip_aport_hooks:
140
+ (. // []) | map(select((((.command // "") | test("aport-(cursor-hook|claude-code-hook)\\.sh$")) | not)));
141
+ .hooks.beforeShellExecution = ((.hooks.beforeShellExecution // []) | strip_aport_hooks) |
142
+ .hooks.preToolUse = ((.hooks.preToolUse // []) | strip_aport_hooks) |
143
+ .hooks.beforeMCPExecution = ((.hooks.beforeMCPExecution // []) | strip_aport_hooks) |
144
+ .hooks.subagentStart = ((.hooks.subagentStart // []) | strip_aport_hooks) |
145
+ if ((.hooks.beforeShellExecution // []) | length) == 0 then del(.hooks.beforeShellExecution) else . end |
146
+ if ((.hooks.preToolUse // []) | length) == 0 then del(.hooks.preToolUse) else . end |
147
+ if ((.hooks.beforeMCPExecution // []) | length) == 0 then del(.hooks.beforeMCPExecution) else . end |
148
+ if ((.hooks.subagentStart // []) | length) == 0 then del(.hooks.subagentStart) else . end |
149
+ if ((.hooks // {}) | keys | length) == 0 then del(.hooks) else . end
150
+ ' "$hooks_file" > "$tmpfile"; then
151
+ backup_file "$hooks_file"
152
+ mv "$tmpfile" "$hooks_file"
153
+ echo " ✅ Removed APort Cursor hook entries from $hooks_file"
154
+ else
155
+ rm -f "$tmpfile"
156
+ log_warn "Failed to clean Cursor hooks at $hooks_file"
157
+ fi
158
+ }
159
+
160
+ cleanup_openclaw_json() {
161
+ local openclaw_json="$1"
162
+ if [[ ! -f "$openclaw_json" ]]; then
163
+ return 0
164
+ fi
165
+ if ! command -v jq &> /dev/null; then
166
+ log_warn "jq not found; leaving OpenClaw JSON config intact at $openclaw_json"
167
+ return 0
168
+ fi
169
+
170
+ local tmpfile
171
+ tmpfile="$(mktemp "${openclaw_json}.XXXXXX")"
172
+ if jq '
173
+ .plugins = (.plugins // {}) |
174
+ .plugins.entries = ((.plugins.entries // {}) | del(.["openclaw-aport"])) |
175
+ .plugins.installs = ((.plugins.installs // {}) | del(.["openclaw-aport"])) |
176
+ .plugins.load = (.plugins.load // {}) |
177
+ .plugins.load.paths = ((.plugins.load.paths // []) | map(select((type == "string" and contains("/openclaw-aport")) | not))) |
178
+ if ((.plugins.entries // {}) | keys | length) == 0 then del(.plugins.entries) else . end |
179
+ if ((.plugins.installs // {}) | keys | length) == 0 then del(.plugins.installs) else . end |
180
+ if ((.plugins.load.paths // []) | length) == 0 then del(.plugins.load.paths) else . end |
181
+ if ((.plugins.load // {}) | keys | length) == 0 then del(.plugins.load) else . end |
182
+ if ((.plugins // {}) | keys | length) == 0 then del(.plugins) else . end
183
+ ' "$openclaw_json" > "$tmpfile"; then
184
+ backup_file "$openclaw_json"
185
+ mv "$tmpfile" "$openclaw_json"
186
+ echo " ✅ Removed APort OpenClaw entries from $openclaw_json"
187
+ else
188
+ rm -f "$tmpfile"
189
+ log_warn "Failed to clean OpenClaw JSON config at $openclaw_json"
190
+ fi
191
+ }
192
+
193
+ cleanup_python_framework() {
194
+ local config_dir="$1"
195
+ remove_dir_if_exists "$config_dir/aport"
196
+ remove_file_if_exists "$config_dir/config.yaml"
197
+ }
198
+
199
+ cleanup_n8n() {
200
+ local config_dir="$1"
201
+ remove_dir_if_exists "$config_dir/aport"
202
+ }
203
+
204
+ cleanup_claude() {
205
+ local settings_file="$config_dir/settings.json"
206
+ remove_dir_if_exists "$config_dir/aport"
207
+ cleanup_claude_settings "$settings_file"
208
+ }
209
+
210
+ cleanup_cursor() {
211
+ local hooks_file="$config_dir/hooks.json"
212
+ remove_dir_if_exists "$config_dir/aport"
213
+ cleanup_cursor_hooks "$hooks_file"
214
+ }
215
+
216
+ cleanup_openclaw() {
217
+ local openclaw_json="$config_dir/openclaw.json"
218
+ remove_dir_if_exists "$config_dir/aport"
219
+ remove_dir_if_exists "$config_dir/extensions/openclaw-aport"
220
+ remove_dir_if_exists "$config_dir/skills/aport-guardrail"
221
+ remove_file_if_exists "$config_dir/.aport-repo"
222
+ remove_file_if_exists "$config_dir/.skills/aport-guardrail.sh"
223
+ remove_file_if_exists "$config_dir/.skills/aport-guardrail-bash.sh"
224
+ remove_file_if_exists "$config_dir/.skills/aport-guardrail-api.sh"
225
+ remove_file_if_exists "$config_dir/.skills/aport-guardrail-v2.sh"
226
+ remove_file_if_exists "$config_dir/.skills/aport-create-passport.sh"
227
+ remove_file_if_exists "$config_dir/.skills/aport-status.sh"
228
+ cleanup_openclaw_json "$openclaw_json"
229
+ if [[ -f "$config_dir/config.yaml" ]]; then
230
+ log_warn "OpenClaw config.yaml may still contain APort plugin config. Review $config_dir/config.yaml if you need a completely pristine OpenClaw config."
231
+ fi
232
+ }
233
+
234
+ confirm_reset
235
+
236
+ echo "[aport] Resetting framework: $framework" >&2
237
+
238
+ case "$framework" in
239
+ claude-code)
240
+ cleanup_claude
241
+ ;;
242
+ cursor)
243
+ cleanup_cursor
244
+ ;;
245
+ openclaw)
246
+ cleanup_openclaw
247
+ ;;
248
+ langchain | crewai | deerflow)
249
+ cleanup_python_framework "$config_dir"
250
+ ;;
251
+ n8n)
252
+ cleanup_n8n "$config_dir"
253
+ ;;
254
+ *)
255
+ log_error "Unsupported framework reset: $framework"
256
+ exit 1
257
+ ;;
258
+ esac
259
+
260
+ echo ""
261
+ echo " Reset complete for $framework."
262
+ echo " Re-run the setup command when you want a fresh install."
263
+ echo ""