claude-warden 1.2.1 → 1.4.0

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Smart command safety filter for Claude Code — parses shell pipelines and evaluates per-command safety rules to auto-approve safe commands and block dangerous ones",
5
5
  "author": {
6
6
  "name": "banyudu"
package/README.md CHANGED
@@ -127,14 +127,14 @@ rules:
127
127
  description: Read-only docker commands
128
128
  ```
129
129
 
130
- ## Feedback and `/warden-allow`
130
+ ## Feedback and `/claude-warden:warden-allow`
131
131
 
132
132
  When Warden blocks or flags a command, it includes a system message explaining:
133
133
 
134
134
  1. **Why** the command was blocked/flagged (per-command reasons)
135
135
  2. **How to allow it** — a ready-to-use YAML snippet for your config
136
136
 
137
- Use the `/warden-allow` slash command to apply the suggested config change. It will ask which scope (project or user) to use.
137
+ Use the `/claude-warden:warden-allow` slash command to apply the suggested config change. It will ask which scope (project or user) to use.
138
138
 
139
139
  ## Built-in defaults
140
140
 
@@ -27,29 +27,45 @@ askOnSubshell: true
27
27
  # Trusted SSH hosts — ssh/scp/rsync to these hosts are auto-allowed.
28
28
  # Remote commands on trusted hosts are recursively evaluated through warden rules.
29
29
  # Supports glob patterns (* wildcards).
30
+ # Entries can be strings (use global overrides) or objects with per-target settings.
30
31
  # trustedSSHHosts:
31
- # - devserver
32
+ # - devserver # string → uses global overrides only
32
33
  # - staging-*
33
34
  # - "*.internal.company.com"
34
- # - 192.168.1.*
35
+ # - name: prod-bastion # object with per-target overrides
36
+ # overrides:
37
+ # alwaysAllow: [systemctl]
35
38
 
36
39
  # Trusted Docker containers — docker exec to these containers are auto-allowed.
37
40
  # Remote commands are recursively evaluated through warden rules.
38
41
  # trustedDockerContainers:
39
- # - my-app
40
- # - dev-*
42
+ # - my-app # string → uses global overrides only
43
+ # - name: dev-container # object with allowAll (skip all checks)
44
+ # allowAll: true
45
+ # - name: staging-* # object with per-target overrides
46
+ # overrides:
47
+ # alwaysAllow: [sudo, apt]
41
48
 
42
49
  # Trusted kubectl contexts — kubectl exec in these contexts are auto-allowed.
43
50
  # Remote commands (after --) are recursively evaluated through warden rules.
44
51
  # trustedKubectlContexts:
45
52
  # - minikube
46
- # - dev-cluster-*
53
+ # - name: dev-cluster-*
54
+ # overrides:
55
+ # alwaysAllow: [sudo]
56
+ # - name: prod-cluster
57
+ # overrides:
58
+ # alwaysDeny: [rm]
47
59
 
48
60
  # Trusted Sprites — sprite exec/console to these sprites are auto-allowed.
49
61
  # Remote commands are recursively evaluated through warden rules.
50
62
  # trustedSprites:
51
- # - my-sprite
52
- # - dev-*
63
+ # - my-sprite # string → uses global overrides only
64
+ # - name: yudu-claw # allowAll: skip all checks (disposable env)
65
+ # allowAll: true
66
+ # - name: dev-* # per-target overrides
67
+ # overrides:
68
+ # alwaysAllow: [sudo, apt]
53
69
 
54
70
  # Override rules when evaluating commands inside trusted remote contexts
55
71
  # (docker exec, kubectl exec, ssh, sprite exec). These overrides are applied
package/dist/index.cjs CHANGED
@@ -18515,8 +18515,8 @@ function globToRegex(pattern) {
18515
18515
  }
18516
18516
  return new RegExp(`^${regex}$`);
18517
18517
  }
18518
- function matchesPattern(value, patterns) {
18519
- return patterns.some((p) => globToRegex(p).test(value));
18518
+ function findMatchingTarget(value, targets) {
18519
+ return targets.find((t) => globToRegex(t.name).test(value)) || null;
18520
18520
  }
18521
18521
  function parseSSHArgs(args2) {
18522
18522
  let host = null;
@@ -18560,19 +18560,21 @@ function evaluateSSHCommand(cmd, config) {
18560
18560
  const trustedHosts = config.trustedSSHHosts || [];
18561
18561
  if (command === "scp" || command === "rsync") {
18562
18562
  const host2 = extractHostFromRemotePath(args2);
18563
- if (host2 && matchesPattern(host2, trustedHosts)) {
18564
- return {
18565
- command,
18566
- args: args2,
18567
- decision: "allow",
18568
- reason: `Trusted SSH host "${host2}"`,
18569
- matchedRule: "trustedSSHHosts"
18570
- };
18571
- }
18572
- return null;
18563
+ if (!host2) return null;
18564
+ const target2 = findMatchingTarget(host2, trustedHosts);
18565
+ if (!target2) return null;
18566
+ return {
18567
+ command,
18568
+ args: args2,
18569
+ decision: "allow",
18570
+ reason: `Trusted SSH host "${host2}"`,
18571
+ matchedRule: "trustedSSHHosts"
18572
+ };
18573
18573
  }
18574
18574
  const { host, remoteCommand } = parseSSHArgs(args2);
18575
- if (!host || !matchesPattern(host, trustedHosts)) return null;
18575
+ if (!host) return null;
18576
+ const target = findMatchingTarget(host, trustedHosts);
18577
+ if (!target) return null;
18576
18578
  if (!remoteCommand) {
18577
18579
  return {
18578
18580
  command,
@@ -18582,8 +18584,17 @@ function evaluateSSHCommand(cmd, config) {
18582
18584
  matchedRule: "trustedSSHHosts"
18583
18585
  };
18584
18586
  }
18587
+ if (target.allowAll) {
18588
+ return {
18589
+ command,
18590
+ args: args2,
18591
+ decision: "allow",
18592
+ reason: `Trusted SSH host "${host}" (allowAll)`,
18593
+ matchedRule: "trustedSSHHosts"
18594
+ };
18595
+ }
18585
18596
  const parsed = parseCommand(remoteCommand);
18586
- const result = evaluate(parsed, configWithContextOverrides(config));
18597
+ const result = evaluate(parsed, configWithContextOverrides(config, target));
18587
18598
  return {
18588
18599
  command,
18589
18600
  args: args2,
@@ -18603,15 +18614,21 @@ var DOCKER_EXEC_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
18603
18614
  "--detach-keys"
18604
18615
  ]);
18605
18616
  var INTERACTIVE_SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh"]);
18606
- function configWithContextOverrides(config) {
18607
- if (!config.trustedContextOverrides) return config;
18617
+ function configWithContextOverrides(config, target) {
18618
+ const overrideLayers = [];
18619
+ if (target?.overrides) overrideLayers.push(target.overrides);
18620
+ if (config.trustedContextOverrides) overrideLayers.push(config.trustedContextOverrides);
18621
+ if (overrideLayers.length === 0) return config;
18608
18622
  return {
18609
18623
  ...config,
18610
- layers: [config.trustedContextOverrides, ...config.layers]
18624
+ layers: [...overrideLayers, ...config.layers]
18611
18625
  };
18612
18626
  }
18613
- function evaluateRemoteCommand(remoteArgs, config) {
18614
- const overriddenConfig = configWithContextOverrides(config);
18627
+ function evaluateRemoteCommand(remoteArgs, config, target) {
18628
+ if (target?.allowAll) {
18629
+ return { decision: "allow", reason: "allowAll target", details: [] };
18630
+ }
18631
+ const overriddenConfig = configWithContextOverrides(config, target);
18615
18632
  if (remoteArgs.length === 0) {
18616
18633
  return { decision: "allow", reason: "interactive", details: [] };
18617
18634
  }
@@ -18625,9 +18642,10 @@ function evaluateRemoteCommand(remoteArgs, config) {
18625
18642
  return evaluate(parsed2, overriddenConfig);
18626
18643
  }
18627
18644
  const parsed = {
18628
- commands: [{ command: remoteCmd, args: remoteArgs.slice(1) }],
18645
+ commands: [{ command: remoteCmd, args: remoteArgs.slice(1), envPrefixes: [], raw: remoteArgs.join(" ") }],
18629
18646
  hasSubshell: false,
18630
- subshellCommands: []
18647
+ subshellCommands: [],
18648
+ parseError: false
18631
18649
  };
18632
18650
  return evaluate(parsed, overriddenConfig);
18633
18651
  }
@@ -18661,14 +18679,16 @@ function parseDockerExecArgs(args2) {
18661
18679
  function evaluateDockerExec(cmd, config) {
18662
18680
  const { command, args: args2 } = cmd;
18663
18681
  if (args2[0] !== "exec") return null;
18664
- const { target, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18665
- if (!target || !matchesPattern(target, config.trustedDockerContainers || [])) return null;
18666
- const result = evaluateRemoteCommand(remoteArgs, config);
18682
+ const { target: containerName, remoteArgs } = parseDockerExecArgs(args2.slice(1));
18683
+ if (!containerName) return null;
18684
+ const matched = findMatchingTarget(containerName, config.trustedDockerContainers || []);
18685
+ if (!matched) return null;
18686
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18667
18687
  return {
18668
18688
  command,
18669
18689
  args: args2,
18670
18690
  decision: result.decision,
18671
- reason: `Trusted Docker container "${target}" (${result.reason})`,
18691
+ reason: `Trusted Docker container "${containerName}" (${result.reason})`,
18672
18692
  matchedRule: "trustedDockerContainers"
18673
18693
  };
18674
18694
  }
@@ -18740,9 +18760,10 @@ function evaluateKubectlExec(cmd, config) {
18740
18760
  const { command, args: args2 } = cmd;
18741
18761
  if (args2[0] !== "exec") return null;
18742
18762
  const { context, pod, remoteArgs } = parseKubectlExecArgs(args2.slice(1));
18743
- const trustedContexts = config.trustedKubectlContexts || [];
18744
- if (!context || !matchesPattern(context, trustedContexts)) return null;
18745
- const result = evaluateRemoteCommand(remoteArgs, config);
18763
+ if (!context) return null;
18764
+ const matched = findMatchingTarget(context, config.trustedKubectlContexts || []);
18765
+ if (!matched) return null;
18766
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18746
18767
  return {
18747
18768
  command,
18748
18769
  args: args2,
@@ -18805,9 +18826,10 @@ function parseSpriteExecArgs(args2) {
18805
18826
  function evaluateSpriteExec(cmd, config) {
18806
18827
  const { command, args: args2 } = cmd;
18807
18828
  const { spriteName, remoteArgs } = parseSpriteExecArgs(args2);
18808
- const trustedSprites = config.trustedSprites || [];
18809
- if (!spriteName || !matchesPattern(spriteName, trustedSprites)) return null;
18810
- const result = evaluateRemoteCommand(remoteArgs, config);
18829
+ if (!spriteName) return null;
18830
+ const matched = findMatchingTarget(spriteName, config.trustedSprites || []);
18831
+ if (!matched) return null;
18832
+ const result = evaluateRemoteCommand(remoteArgs, config, matched);
18811
18833
  return {
18812
18834
  command,
18813
18835
  args: args2,
@@ -19058,6 +19080,19 @@ var DEFAULT_CONFIG = {
19058
19080
  "yq",
19059
19081
  "xargs",
19060
19082
  "seq",
19083
+ // Network diagnostics (read-only)
19084
+ "nslookup",
19085
+ "dig",
19086
+ "host",
19087
+ "ping",
19088
+ "traceroute",
19089
+ "mtr",
19090
+ "netstat",
19091
+ "ss",
19092
+ "ifconfig",
19093
+ "ip",
19094
+ "nmap",
19095
+ "arp",
19061
19096
  // Pagers and formatters
19062
19097
  "bat",
19063
19098
  "pygmentize",
@@ -19072,6 +19107,74 @@ var DEFAULT_CONFIG = {
19072
19107
  "tput",
19073
19108
  "reset",
19074
19109
  "clear",
19110
+ // System/hardware info
19111
+ "lscpu",
19112
+ "lsblk",
19113
+ "lsusb",
19114
+ "lspci",
19115
+ "lsmod",
19116
+ "dmesg",
19117
+ "sysctl",
19118
+ "sw_vers",
19119
+ "system_profiler",
19120
+ "hostinfo",
19121
+ "lsb_release",
19122
+ "hostnamectl",
19123
+ "arch",
19124
+ "getconf",
19125
+ // User/group info
19126
+ "groups",
19127
+ "getent",
19128
+ "w",
19129
+ "last",
19130
+ "lastlog",
19131
+ "finger",
19132
+ "users",
19133
+ // Process info
19134
+ "pgrep",
19135
+ "pidof",
19136
+ "jobs",
19137
+ // Compression/archive
19138
+ "tar",
19139
+ "gzip",
19140
+ "gunzip",
19141
+ "bzip2",
19142
+ "bunzip2",
19143
+ "xz",
19144
+ "unxz",
19145
+ "zip",
19146
+ "unzip",
19147
+ "7z",
19148
+ "zcat",
19149
+ "bzcat",
19150
+ "xzcat",
19151
+ "zless",
19152
+ "zmore",
19153
+ "zgrep",
19154
+ // Clipboard
19155
+ "pbcopy",
19156
+ "pbpaste",
19157
+ "xclip",
19158
+ "xsel",
19159
+ "wl-copy",
19160
+ "wl-paste",
19161
+ // Binary analysis
19162
+ "strings",
19163
+ "nm",
19164
+ "objdump",
19165
+ "readelf",
19166
+ "ldd",
19167
+ "otool",
19168
+ "size",
19169
+ // macOS utilities (read-only)
19170
+ "mdfind",
19171
+ "mdls",
19172
+ "mdutil",
19173
+ "plutil",
19174
+ "sips",
19175
+ "xcode-select",
19176
+ "xcrun",
19177
+ "xcodebuild",
19075
19178
  // Misc safe
19076
19179
  "cd",
19077
19180
  "pushd",
@@ -19089,7 +19192,18 @@ var DEFAULT_CONFIG = {
19089
19192
  "shasum",
19090
19193
  "cksum",
19091
19194
  "base64",
19092
- "openssl"
19195
+ "openssl",
19196
+ "watch",
19197
+ "timeout",
19198
+ "nohup",
19199
+ "nice",
19200
+ "iconv",
19201
+ "locale",
19202
+ "localedef",
19203
+ "numfmt",
19204
+ "factor",
19205
+ "bc",
19206
+ "dc"
19093
19207
  ],
19094
19208
  alwaysDeny: [
19095
19209
  "sudo",
@@ -19113,7 +19227,9 @@ var DEFAULT_CONFIG = {
19113
19227
  "crontab",
19114
19228
  "systemctl",
19115
19229
  "service",
19116
- "launchctl"
19230
+ "launchctl",
19231
+ "wipefs",
19232
+ "shred"
19117
19233
  ],
19118
19234
  rules: [
19119
19235
  // --- CLI tools ---
@@ -19228,7 +19344,15 @@ var DEFAULT_CONFIG = {
19228
19344
  ]
19229
19345
  },
19230
19346
  { command: "docker-compose", default: "ask" },
19231
- { command: "kubectl", default: "ask" },
19347
+ {
19348
+ command: "kubectl",
19349
+ default: "ask",
19350
+ argPatterns: [
19351
+ { match: { anyArgMatches: ["^(get|describe|logs|top|explain|api-resources|api-versions|version|config|cluster-info)$"] }, decision: "allow", description: "Read-only kubectl commands" },
19352
+ { match: { anyArgMatches: ["^(delete|drain|cordon|taint)$"] }, decision: "ask", reason: "Destructive kubectl operation" },
19353
+ VERSION_HELP_FLAGS
19354
+ ]
19355
+ },
19232
19356
  // --- File operations ---
19233
19357
  {
19234
19358
  command: "rm",
@@ -19268,7 +19392,92 @@ var DEFAULT_CONFIG = {
19268
19392
  // --- Terraform / IaC ---
19269
19393
  { command: "terraform", default: "ask", argPatterns: [
19270
19394
  { match: { anyArgMatches: ["^(plan|validate|fmt|show|state|output|providers|version|graph|console)$"] }, decision: "allow", description: "Read-only terraform commands" }
19271
- ] }
19395
+ ] },
19396
+ // --- macOS open ---
19397
+ { command: "open", default: "ask" },
19398
+ // --- Text editors ---
19399
+ ...["vi", "vim", "nvim", "nano", "emacs"].map((cmd) => ({
19400
+ command: cmd,
19401
+ default: "ask",
19402
+ argPatterns: [VERSION_HELP_FLAGS]
19403
+ })),
19404
+ // --- Scripting languages ---
19405
+ ...["ruby", "perl", "php"].map((cmd) => ({
19406
+ command: cmd,
19407
+ default: "ask",
19408
+ argPatterns: [
19409
+ { match: { anyArgMatches: ["^-e$", "^--eval"] }, decision: "ask", reason: "Inline code execution" },
19410
+ VERSION_HELP_FLAGS
19411
+ ]
19412
+ })),
19413
+ // --- Java ecosystem ---
19414
+ { command: "java", default: "ask", argPatterns: [VERSION_HELP_FLAGS] },
19415
+ { command: "javac", default: "allow" },
19416
+ // --- Swift / Zig / Dotnet ---
19417
+ { command: "swift", default: "allow", argPatterns: [
19418
+ { match: { anyArgMatches: ["^(build|test|run|package)$"] }, decision: "allow" }
19419
+ ] },
19420
+ { command: "swiftc", default: "allow" },
19421
+ { command: "zig", default: "allow" },
19422
+ { command: "dotnet", default: "allow", argPatterns: [
19423
+ { match: { anyArgMatches: ["^(publish|nuget)$"] }, decision: "ask", reason: "Publishing" }
19424
+ ] },
19425
+ // --- Database CLIs ---
19426
+ ...["psql", "mysql", "mariadb", "sqlite3", "redis-cli", "mongosh"].map((cmd) => ({
19427
+ command: cmd,
19428
+ default: "ask",
19429
+ argPatterns: [VERSION_HELP_FLAGS]
19430
+ })),
19431
+ // --- Cloud CLIs ---
19432
+ { command: "gcloud", default: "ask", argPatterns: [
19433
+ { match: { anyArgMatches: ["^(info|version|help|config|components)$"] }, decision: "allow", description: "Config/info" },
19434
+ { match: { anyArgMatches: ["^(list|describe|get-iam-policy|get)$"] }, decision: "allow", description: "Read-only ops" },
19435
+ VERSION_HELP_FLAGS
19436
+ ] },
19437
+ { command: "az", default: "ask", argPatterns: [
19438
+ { match: { anyArgMatches: ["^(list|show|get)$"] }, decision: "allow", description: "Read-only ops" },
19439
+ VERSION_HELP_FLAGS
19440
+ ] },
19441
+ { command: "aws", default: "ask", argPatterns: [
19442
+ { match: { anyArgMatches: ["^(describe|list|get|sts)$"] }, decision: "allow", description: "Read-only ops" },
19443
+ VERSION_HELP_FLAGS
19444
+ ] },
19445
+ // --- Helm ---
19446
+ { command: "helm", default: "ask", argPatterns: [
19447
+ { match: { anyArgMatches: ["^(list|search|show|status|get|template|version|env|history)$"] }, decision: "allow", description: "Read-only helm commands" },
19448
+ VERSION_HELP_FLAGS
19449
+ ] },
19450
+ // --- Screen/tmux ---
19451
+ ...["screen", "tmux"].map((cmd) => ({
19452
+ command: cmd,
19453
+ default: "ask",
19454
+ argPatterns: [
19455
+ { match: { anyArgMatches: ["^(list-sessions|ls|list)$"] }, decision: "allow", description: "List sessions" },
19456
+ VERSION_HELP_FLAGS
19457
+ ]
19458
+ })),
19459
+ // --- GPG ---
19460
+ { command: "gpg", default: "ask", argPatterns: [
19461
+ { match: { anyArgMatches: ["^(--verify|--list-keys|--list-secret-keys|--fingerprint)$"] }, decision: "allow", description: "Read-only gpg" },
19462
+ VERSION_HELP_FLAGS
19463
+ ] },
19464
+ // --- macOS-specific ---
19465
+ { command: "defaults", default: "ask", argPatterns: [
19466
+ { match: { anyArgMatches: ["^(read|read-type|find|domains)$"] }, decision: "allow", description: "Read-only defaults operations" }
19467
+ ] },
19468
+ { command: "diskutil", default: "ask", argPatterns: [
19469
+ { match: { anyArgMatches: ["^(list|info|apfs|cs|appleRAID)$"] }, decision: "allow", description: "Read-only diskutil" }
19470
+ ] },
19471
+ { command: "codesign", default: "ask", argPatterns: [
19472
+ { match: { anyArgMatches: ["^(-vv|--verify|--display|-d)$"] }, decision: "allow", description: "Verify codesign" }
19473
+ ] },
19474
+ { command: "osascript", default: "ask" },
19475
+ { command: "say", default: "ask" },
19476
+ // --- Process management ---
19477
+ { command: "kill", default: "ask" },
19478
+ { command: "killall", default: "ask" },
19479
+ { command: "pkill", default: "ask" },
19480
+ { command: "renice", default: "ask" }
19272
19481
  ]
19273
19482
  }]
19274
19483
  };
@@ -19335,18 +19544,33 @@ function extractLayer(raw) {
19335
19544
  rules: Array.isArray(raw.rules) ? raw.rules : []
19336
19545
  };
19337
19546
  }
19547
+ function parseTrustedList(raw) {
19548
+ return raw.map((entry) => {
19549
+ if (typeof entry === "string") return { name: entry };
19550
+ if (entry && typeof entry === "object" && "name" in entry) {
19551
+ const obj = entry;
19552
+ const target = { name: String(obj.name) };
19553
+ if (obj.allowAll === true) target.allowAll = true;
19554
+ if (obj.overrides && typeof obj.overrides === "object") {
19555
+ target.overrides = extractLayer(obj.overrides);
19556
+ }
19557
+ return target;
19558
+ }
19559
+ return null;
19560
+ }).filter((t) => t !== null);
19561
+ }
19338
19562
  function mergeNonLayerFields(config, raw) {
19339
19563
  if (Array.isArray(raw.trustedSSHHosts)) {
19340
- config.trustedSSHHosts = [...config.trustedSSHHosts || [], ...raw.trustedSSHHosts];
19564
+ config.trustedSSHHosts = [...config.trustedSSHHosts || [], ...parseTrustedList(raw.trustedSSHHosts)];
19341
19565
  }
19342
19566
  if (Array.isArray(raw.trustedDockerContainers)) {
19343
- config.trustedDockerContainers = [...config.trustedDockerContainers || [], ...raw.trustedDockerContainers];
19567
+ config.trustedDockerContainers = [...config.trustedDockerContainers || [], ...parseTrustedList(raw.trustedDockerContainers)];
19344
19568
  }
19345
19569
  if (Array.isArray(raw.trustedKubectlContexts)) {
19346
- config.trustedKubectlContexts = [...config.trustedKubectlContexts || [], ...raw.trustedKubectlContexts];
19570
+ config.trustedKubectlContexts = [...config.trustedKubectlContexts || [], ...parseTrustedList(raw.trustedKubectlContexts)];
19347
19571
  }
19348
19572
  if (Array.isArray(raw.trustedSprites)) {
19349
- config.trustedSprites = [...config.trustedSprites || [], ...raw.trustedSprites];
19573
+ config.trustedSprites = [...config.trustedSprites || [], ...parseTrustedList(raw.trustedSprites)];
19350
19574
  }
19351
19575
  if (typeof raw.defaultDecision === "string") {
19352
19576
  config.defaultDecision = raw.defaultDecision;
@@ -19408,15 +19632,15 @@ function formatSystemMessage(decision, rawCommand, details) {
19408
19632
  const header = `[warden] ${parts.join(" | ")}`;
19409
19633
  const subcommandHints = relevant.filter((d) => d.args.length > 0).map((d) => {
19410
19634
  const sub = d.args[0];
19411
- return ` Option A: Allow all \`${d.command}\` \u2192 \`/warden-allow ${d.command}\`
19412
- Option B: Allow only \`${d.command} ${sub}\` \u2192 \`/warden-allow ${d.command} ${sub}\``;
19635
+ return ` Option A: Allow all \`${d.command}\` \u2192 \`/claude-warden:warden-allow ${d.command}\`
19636
+ Option B: Allow only \`${d.command} ${sub}\` \u2192 \`/claude-warden:warden-allow ${d.command} ${sub}\``;
19413
19637
  });
19414
19638
  if (subcommandHints.length > 0) {
19415
19639
  return `${header}
19416
19640
  ${subcommandHints.join("\n")}
19417
- See /warden-allow`;
19641
+ See /claude-warden:warden-allow`;
19418
19642
  }
19419
- return `${header} \u2014 To auto-allow, see /warden-allow`;
19643
+ return `${header} \u2014 To auto-allow, see /claude-warden:warden-allow`;
19420
19644
  }
19421
19645
  const lines = ["[warden] Command blocked", ""];
19422
19646
  if (relevant.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "Smart command safety filter for Claude Code — auto-approves safe commands, blocks dangerous ones",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",