Skip to content

Commit fcf1177

Browse files
authored
feat: add /passthru:list and /passthru:remove commands (#10)
1 parent 3060e2b commit fcf1177

11 files changed

Lines changed: 2040 additions & 8 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "passthru",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "Regex-based permission rules for Claude Code via hooks",
55
"owner": {
66
"name": "nnemirovsky"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "passthru",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "Regex-based permission rules for Claude Code via hooks",
55
"license": "MIT"
66
}

CLAUDE.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ commands/
1212
bootstrap.md /passthru:bootstrap slash command (wraps scripts/bootstrap.sh with dry-run + confirm)
1313
add.md /passthru:add slash command (prompt-based)
1414
suggest.md /passthru:suggest slash command (prompt-based)
15+
list.md /passthru:list slash command (wraps scripts/list.sh)
16+
remove.md /passthru:remove slash command (wraps scripts/remove-rule.sh)
1517
verify.md /passthru:verify slash command (prompt-based)
1618
log.md /passthru:log slash command (prompt-based)
1719
hooks/
@@ -37,7 +39,9 @@ scripts/
3739
Supported shapes: Bash(prefix:*) | Bash(exact) | mcp__* | WebFetch(domain:X)
3840
| WebSearch | Read/Edit/Write(path[/**]) | Skill(name). Others -> [WARN] skip.
3941
write-rule.sh atomic write wrapper: backup + append + verify + rollback
40-
verify.sh rule verifier CLI (also invoked by write-rule.sh and /passthru:verify)
42+
remove-rule.sh atomic remove wrapper: backup + splice + verify + rollback. Authored-only.
43+
list.sh rule list viewer CLI with scope/list/source/index annotations
44+
verify.sh rule verifier CLI (also invoked by write-rule.sh/remove-rule.sh and /passthru:verify)
4145
log.sh audit-log viewer CLI + sentinel toggle
4246
tests/
4347
fixtures/ JSON fixture files used by bats tests
@@ -133,11 +137,12 @@ Checks performed (in order, across the merged set):
133137
## Write-wrapper locking
134138

135139
`scripts/write-rule.sh` (also called by `bootstrap.sh --write` and the
136-
`/passthru:add`, `/passthru:suggest` commands) serializes concurrent writers
137-
via a single user-scope lock directory at
138-
`~/.claude/passthru.write.lock.d`. The lock uses `mkdir`, which is atomic on
139-
every POSIX filesystem we target (local Linux/macOS plus NFS), works without
140-
any extra dependency, and polls at 100 ms intervals while waiting.
140+
`/passthru:add`, `/passthru:suggest` commands) and `scripts/remove-rule.sh`
141+
(called by `/passthru:remove`) serialize concurrent mutations via a single
142+
user-scope lock directory at `~/.claude/passthru.write.lock.d`. The lock
143+
uses `mkdir`, which is atomic on every POSIX filesystem we target (local
144+
Linux/macOS plus NFS), works without any extra dependency, and polls at
145+
100 ms intervals while waiting.
141146

142147
The lock-acquisition timeout is 5 seconds by default and is configurable via
143148
`PASSTHRU_WRITE_LOCK_TIMEOUT=<seconds>` in the environment. Both

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ All commands are plugin-namespaced under `/passthru:`.
4646
| `/passthru:bootstrap` | One-shot importer: reviews your existing `permissions.allow` entries, shows the proposed rules, asks to confirm, then writes `passthru.imported.json`. Runs the verifier afterwards. |
4747
| `/passthru:add` | Add a rule without hand-editing `passthru.json`. Supports `--deny` and `--field`. |
4848
| `/passthru:suggest` | Propose a generalized rule from a recent tool call in the conversation, then write it on confirmation. |
49+
| `/passthru:list` | Show every rule across user and project scopes, grouped by `(scope, list, source)` with 1-based indexes. Filter by `--scope`, `--list`, `--source`, or `--tool`. |
50+
| `/passthru:remove` | Remove an authored rule by `<scope> <list> <index>`. Indexes match the numbering from `/passthru:list`. Imported (bootstrap-generated) rules are not removable here; edit `settings.json` and re-run bootstrap instead. |
4951
| `/passthru:verify` | Validate every rule file. Surfaces parse errors, schema violations, invalid regex, duplicates, and allow/deny conflicts. |
5052
| `/passthru:log` | Read the audit log with filters. Also toggles the audit sentinel on/off. |
5153

@@ -195,6 +197,30 @@ Propose a generalized rule from a recent tool call in the conversation. The comm
195197
/passthru:suggest gh api
196198
```
197199

200+
### `/passthru:list`
201+
202+
Show every rule across user and project scopes. Rules are grouped by `(scope, list, source)` and numbered with 1-based indexes that match what `/passthru:remove` expects.
203+
204+
```
205+
/passthru:list
206+
/passthru:list --scope user --list deny
207+
/passthru:list --source imported
208+
/passthru:list --tool '^Bash$'
209+
/passthru:list --flat
210+
/passthru:list --format json
211+
```
212+
213+
### `/passthru:remove`
214+
215+
Remove an authored rule by `<scope> <list> <index>`. Run `/passthru:list` first to see the indexes.
216+
217+
```
218+
/passthru:remove user allow 3
219+
/passthru:remove project deny 1
220+
```
221+
222+
Imported rules (written by `/passthru:bootstrap`) are not removable here because bootstrap regenerates them on every run. To drop one, remove the corresponding `permissions.allow` entry from `settings.json` and re-run bootstrap.
223+
198224
### `/passthru:verify`
199225

200226
Validate every rule file. Surfaces parse errors, schema violations, invalid regex, duplicates, and allow+deny conflicts.

commands/list.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
description: List passthru rules with scope, source, list, and index annotations
3+
argument-hint: "[--scope user|project|all] [--list allow|deny|all] [--source authored|imported|all] [--tool <regex>] [--format table|json|raw] [--flat]"
4+
---
5+
6+
# /passthru:list
7+
8+
Show every passthru rule across user and project scopes, grouped by
9+
`(scope, list, source)` with 1-based indexes that match the numbering
10+
`/passthru:remove` expects.
11+
12+
Rules come from up to four files:
13+
14+
* `~/.claude/passthru.json` - user-authored
15+
* `~/.claude/passthru.imported.json` - user-imported (written by
16+
`scripts/bootstrap.sh`)
17+
* `$PWD/.claude/passthru.json` - project-authored
18+
* `$PWD/.claude/passthru.imported.json` - project-imported
19+
20+
## What you must do
21+
22+
You are Claude. Shell out to the list script with `$ARGUMENTS` passed
23+
verbatim, then present the output. Do not paraphrase table rows; quote
24+
them verbatim.
25+
26+
### 1. Run the list viewer
27+
28+
Invoke exactly:
29+
30+
```bash
31+
bash ${CLAUDE_PLUGIN_ROOT}/scripts/list.sh $ARGUMENTS
32+
```
33+
34+
Capture stdout, stderr, and the exit code. The script accepts:
35+
36+
* `--scope user|project|all` - default `all`.
37+
* `--list allow|deny|all` - default `all`.
38+
* `--source authored|imported|all` - default `all`. Useful to see only
39+
bootstrap imports, or to check the hand-curated authored set.
40+
* `--tool <regex>` - perl regex matched against the `.tool` field.
41+
* `--format table|json|raw` - default `table` (colorized when stdout is
42+
a tty), `json` emits an array of annotated rule objects (scope,
43+
source, list, index, path, rule), `raw` emits one rule JSON per line
44+
with annotations stripped.
45+
* `--flat` - skip grouped rendering and emit one flat table with
46+
`scope`, `list`, `source`, `#` columns in front.
47+
48+
### 2. Present the result
49+
50+
Branch on the exit code:
51+
52+
**Exit 0, stdout has rows:** show the grouped (or flat) table verbatim.
53+
Optionally add a one-liner summarizing the filter that was applied so
54+
the user knows which rules they are looking at.
55+
56+
**Exit 0, stderr says `no rules found`:** no rule files exist yet, or
57+
the filter matched nothing. Suggest `/passthru:add` to add a first rule
58+
or `/passthru:bootstrap` to import from native settings.
59+
60+
**Exit 2:** the user passed a bad `--tool` regex or an unknown flag.
61+
Surface stderr verbatim and suggest re-running with `--help`.
62+
63+
### 3. Guidance
64+
65+
* Point the user at `/passthru:remove <scope> <list> <index>` when they
66+
ask how to delete a rule. The indexes in this command's output line
67+
up with what `remove` expects.
68+
* Imported rules are regenerated by `/passthru:bootstrap`. If the user
69+
wants to edit them, they should change the underlying
70+
`permissions.allow` entries in their `settings.json` and re-run
71+
bootstrap, not hand-edit the imported file.
72+
* `--format raw` is the right choice for piping into `jq` for custom
73+
queries without quoting hassle.
74+
75+
## Examples
76+
77+
* Show everything (default grouping):
78+
79+
```
80+
/passthru:list
81+
```
82+
83+
* Show only deny rules in the user scope:
84+
85+
```
86+
/passthru:list --scope user --list deny
87+
```
88+
89+
* Show only what was imported by bootstrap:
90+
91+
```
92+
/passthru:list --source imported
93+
```
94+
95+
* Filter by tool:
96+
97+
```
98+
/passthru:list --tool '^Bash$'
99+
```
100+
101+
* Emit a flat table for easy grepping:
102+
103+
```
104+
/passthru:list --flat
105+
```
106+
107+
* JSON for scripting:
108+
109+
```
110+
/passthru:list --format json
111+
```

commands/remove.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
description: Remove an authored passthru rule by scope, list, and 1-based index
3+
argument-hint: "<scope> <list> <index>"
4+
---
5+
6+
# /passthru:remove
7+
8+
Remove a single authored passthru rule by scope, list, and 1-based
9+
index. Imported rules (written by `scripts/bootstrap.sh`) are not
10+
removable here because bootstrap regenerates them on every run. To
11+
drop an imported rule, remove the corresponding `permissions.allow`
12+
entry from your `settings.json` and re-run bootstrap.
13+
14+
Run `/passthru:list` first to see the indexes.
15+
16+
## What you must do
17+
18+
You are Claude. Parse `$ARGUMENTS` and shell out to the remove script.
19+
Do not invent behaviour. Surface errors verbatim.
20+
21+
### 1. Tokenize `$ARGUMENTS`
22+
23+
Three positional tokens are required, in this order:
24+
25+
1. `scope` - must be `user` or `project`.
26+
2. `list` - must be `allow` or `deny`.
27+
3. `index` - positive integer, 1-based, from the `#` column of
28+
`/passthru:list` under the matching `(scope, list, authored-source)`
29+
group.
30+
31+
If any are missing or malformed, tell the user:
32+
`usage: /passthru:remove <scope> <list> <index>` and stop.
33+
34+
### 2. Invoke the remove wrapper
35+
36+
Run exactly:
37+
38+
```bash
39+
bash ${CLAUDE_PLUGIN_ROOT}/scripts/remove-rule.sh <scope> <list> <index>
40+
```
41+
42+
Capture stdout, stderr, and the exit code.
43+
44+
### 3. Handle the result
45+
46+
**Exit 0:** the script prints
47+
`removed <scope>/<list>/<index>: <tool-summary>` on stdout. Echo that
48+
back to the user plus a reminder that the file is now one rule
49+
shorter. Optionally offer to run `/passthru:list` to confirm.
50+
51+
**Exit 1:** invalid args, rule not found, or the user tried to remove
52+
an imported rule. Surface stderr verbatim. If the message mentions
53+
`cannot remove imported rule`, explain that bootstrap-managed rules
54+
are regenerated from `settings.json` and suggest editing that file
55+
followed by `/passthru:bootstrap --write`.
56+
57+
**Exit 2:** the verifier rejected the post-remove state. The remove
58+
script has already restored the backup. Show the verifier's stderr
59+
verbatim and suggest `/passthru:verify` for a full report. This is
60+
rare in practice since removing a rule cannot introduce a new
61+
violation.
62+
63+
### 4. Guidance
64+
65+
* Recommend `/passthru:list` before `/passthru:remove` so the user
66+
sees live indexes. Indexes shift down after a remove, so consecutive
67+
removes should re-check the list.
68+
* Remember that `remove` only operates on authored files
69+
(`passthru.json`), never on imported ones.
70+
71+
## Examples
72+
73+
* Remove the 3rd authored allow rule from the user scope:
74+
75+
```
76+
/passthru:remove user allow 3
77+
```
78+
79+
* Remove the first authored deny rule from the project scope:
80+
81+
```
82+
/passthru:remove project deny 1
83+
```
84+
85+
* Typical workflow:
86+
87+
```
88+
/passthru:list
89+
/passthru:remove user allow 2
90+
/passthru:list # confirm the remaining rules
91+
```

0 commit comments

Comments
 (0)