claude-warden 2.7.0 → 2.8.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.
@@ -8,7 +8,7 @@
8
8
  {
9
9
  "name": "warden",
10
10
  "description": "Auto-approves safe commands, blocks dangerous ones, prompts for the rest",
11
- "version": "2.7.0",
11
+ "version": "2.8.0",
12
12
  "author": {
13
13
  "name": "banyudu"
14
14
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "warden",
3
- "version": "2.7.0",
3
+ "version": "2.8.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"
@@ -19,6 +19,26 @@ askOnSubshell: true
19
19
  notifyOnAsk: true
20
20
  notifyOnDeny: true
21
21
 
22
+ # Guidance injected into every Claude Code session via the SessionStart hook.
23
+ # Claude sees this as a system message, so it can shape tool choice *before*
24
+ # warden needs to ask or deny. Keep it short — it competes for context.
25
+ #
26
+ # - Omit the key to use the built-in default (prefer jq, save temp scripts to
27
+ # tempScriptDir below, read deny reasons, mention /warden:allow and /warden:yolo).
28
+ # - Set to a string to override with your own guidance.
29
+ # - Set to `false` to disable injection entirely.
30
+ #
31
+ # sessionGuidance: |
32
+ # Warden is active. Prefer allow-listed tools (e.g. jq for JSON). For
33
+ # multi-line logic, save a temp script under /tmp/ instead of using
34
+ # inline `bash -c` / `node -e`.
35
+
36
+ # Directory the built-in guidance tells Claude to save throwaway multi-line
37
+ # scripts to. Defaults to `/tmp` so scripts don't pollute the repo. Point at
38
+ # another location (e.g. `.warden-scratch`) if you want them tracked per-project.
39
+ # Only used when `sessionGuidance` is unset.
40
+ # tempScriptDir: /tmp
41
+
22
42
  # Additional commands to always allow (checked after alwaysDeny within this scope)
23
43
  # alwaysAllow:
24
44
  # - terraform
package/dist/cli.cjs CHANGED
@@ -18933,6 +18933,18 @@ var DEFAULT_SKILL_RULES = {
18933
18933
  rules: []
18934
18934
  }]
18935
18935
  };
18936
+ var DEFAULT_TEMP_SCRIPT_DIR = "/tmp";
18937
+ function buildDefaultSessionGuidance(tempScriptDir) {
18938
+ return [
18939
+ "Claude Warden is active. It filters Bash commands against safety rules and may ask or deny.",
18940
+ "",
18941
+ "- For JSON in shell pipelines, prefer `jq` (auto-allowed) over `python3 -c` / `node -e`.",
18942
+ `- For multi-line logic, save a temp script under \`${tempScriptDir}/\` (e.g. \`${tempScriptDir}/warden-task.sh\`) or add a \`package.json\` script rather than inline \`bash -c\` / \`node -e\`. Avoid polluting the repo with throwaway scripts.`,
18943
+ "- When Warden denies or asks, read the reason \u2014 it often names the preferred alternative.",
18944
+ "- To permanently allow a specific command, run `/warden:allow <cmd>`. To temporarily bypass filtering, `/warden:yolo`."
18945
+ ].join("\n");
18946
+ }
18947
+ var DEFAULT_SESSION_GUIDANCE = buildDefaultSessionGuidance(DEFAULT_TEMP_SCRIPT_DIR);
18936
18948
  var DEFAULT_CONFIG = {
18937
18949
  defaultDecision: "ask",
18938
18950
  askOnSubshell: true,
@@ -19809,6 +19821,18 @@ function mergeNonLayerFields(config, raw) {
19809
19821
  if (typeof raw.notifyOnDeny === "boolean") {
19810
19822
  config.notifyOnDeny = raw.notifyOnDeny;
19811
19823
  }
19824
+ if (typeof raw.sessionGuidance === "string" || raw.sessionGuidance === false) {
19825
+ config.sessionGuidance = raw.sessionGuidance;
19826
+ } else if (raw.sessionGuidance !== void 0) {
19827
+ warn(`[warden] Warning: invalid sessionGuidance (expected string or false), ignoring
19828
+ `);
19829
+ }
19830
+ if (typeof raw.tempScriptDir === "string" && raw.tempScriptDir.length > 0) {
19831
+ config.tempScriptDir = raw.tempScriptDir;
19832
+ } else if (raw.tempScriptDir !== void 0) {
19833
+ warn(`[warden] Warning: invalid tempScriptDir (expected non-empty string), ignoring
19834
+ `);
19835
+ }
19812
19836
  if (raw.skills && typeof raw.skills === "object") {
19813
19837
  const skills = raw.skills;
19814
19838
  if (typeof skills.defaultDecision === "string") {
@@ -18937,6 +18937,18 @@ var DEFAULT_SKILL_RULES = {
18937
18937
  rules: []
18938
18938
  }]
18939
18939
  };
18940
+ var DEFAULT_TEMP_SCRIPT_DIR = "/tmp";
18941
+ function buildDefaultSessionGuidance(tempScriptDir) {
18942
+ return [
18943
+ "Claude Warden is active. It filters Bash commands against safety rules and may ask or deny.",
18944
+ "",
18945
+ "- For JSON in shell pipelines, prefer `jq` (auto-allowed) over `python3 -c` / `node -e`.",
18946
+ `- For multi-line logic, save a temp script under \`${tempScriptDir}/\` (e.g. \`${tempScriptDir}/warden-task.sh\`) or add a \`package.json\` script rather than inline \`bash -c\` / \`node -e\`. Avoid polluting the repo with throwaway scripts.`,
18947
+ "- When Warden denies or asks, read the reason \u2014 it often names the preferred alternative.",
18948
+ "- To permanently allow a specific command, run `/warden:allow <cmd>`. To temporarily bypass filtering, `/warden:yolo`."
18949
+ ].join("\n");
18950
+ }
18951
+ var DEFAULT_SESSION_GUIDANCE = buildDefaultSessionGuidance(DEFAULT_TEMP_SCRIPT_DIR);
18940
18952
  var DEFAULT_CONFIG = {
18941
18953
  defaultDecision: "ask",
18942
18954
  askOnSubshell: true,
@@ -19813,6 +19825,18 @@ function mergeNonLayerFields(config, raw) {
19813
19825
  if (typeof raw.notifyOnDeny === "boolean") {
19814
19826
  config.notifyOnDeny = raw.notifyOnDeny;
19815
19827
  }
19828
+ if (typeof raw.sessionGuidance === "string" || raw.sessionGuidance === false) {
19829
+ config.sessionGuidance = raw.sessionGuidance;
19830
+ } else if (raw.sessionGuidance !== void 0) {
19831
+ warn(`[warden] Warning: invalid sessionGuidance (expected string or false), ignoring
19832
+ `);
19833
+ }
19834
+ if (typeof raw.tempScriptDir === "string" && raw.tempScriptDir.length > 0) {
19835
+ config.tempScriptDir = raw.tempScriptDir;
19836
+ } else if (raw.tempScriptDir !== void 0) {
19837
+ warn(`[warden] Warning: invalid tempScriptDir (expected non-empty string), ignoring
19838
+ `);
19839
+ }
19816
19840
  if (raw.skills && typeof raw.skills === "object") {
19817
19841
  const skills = raw.skills;
19818
19842
  if (typeof skills.defaultDecision === "string") {
package/dist/copilot.cjs CHANGED
@@ -18933,6 +18933,18 @@ var DEFAULT_SKILL_RULES = {
18933
18933
  rules: []
18934
18934
  }]
18935
18935
  };
18936
+ var DEFAULT_TEMP_SCRIPT_DIR = "/tmp";
18937
+ function buildDefaultSessionGuidance(tempScriptDir) {
18938
+ return [
18939
+ "Claude Warden is active. It filters Bash commands against safety rules and may ask or deny.",
18940
+ "",
18941
+ "- For JSON in shell pipelines, prefer `jq` (auto-allowed) over `python3 -c` / `node -e`.",
18942
+ `- For multi-line logic, save a temp script under \`${tempScriptDir}/\` (e.g. \`${tempScriptDir}/warden-task.sh\`) or add a \`package.json\` script rather than inline \`bash -c\` / \`node -e\`. Avoid polluting the repo with throwaway scripts.`,
18943
+ "- When Warden denies or asks, read the reason \u2014 it often names the preferred alternative.",
18944
+ "- To permanently allow a specific command, run `/warden:allow <cmd>`. To temporarily bypass filtering, `/warden:yolo`."
18945
+ ].join("\n");
18946
+ }
18947
+ var DEFAULT_SESSION_GUIDANCE = buildDefaultSessionGuidance(DEFAULT_TEMP_SCRIPT_DIR);
18936
18948
  var DEFAULT_CONFIG = {
18937
18949
  defaultDecision: "ask",
18938
18950
  askOnSubshell: true,
@@ -19806,6 +19818,18 @@ function mergeNonLayerFields(config, raw) {
19806
19818
  if (typeof raw.notifyOnDeny === "boolean") {
19807
19819
  config.notifyOnDeny = raw.notifyOnDeny;
19808
19820
  }
19821
+ if (typeof raw.sessionGuidance === "string" || raw.sessionGuidance === false) {
19822
+ config.sessionGuidance = raw.sessionGuidance;
19823
+ } else if (raw.sessionGuidance !== void 0) {
19824
+ warn(`[warden] Warning: invalid sessionGuidance (expected string or false), ignoring
19825
+ `);
19826
+ }
19827
+ if (typeof raw.tempScriptDir === "string" && raw.tempScriptDir.length > 0) {
19828
+ config.tempScriptDir = raw.tempScriptDir;
19829
+ } else if (raw.tempScriptDir !== void 0) {
19830
+ warn(`[warden] Warning: invalid tempScriptDir (expected non-empty string), ignoring
19831
+ `);
19832
+ }
19809
19833
  if (raw.skills && typeof raw.skills === "object") {
19810
19834
  const skills = raw.skills;
19811
19835
  if (typeof skills.defaultDecision === "string") {
package/dist/index.cjs CHANGED
@@ -18933,6 +18933,18 @@ var DEFAULT_SKILL_RULES = {
18933
18933
  rules: []
18934
18934
  }]
18935
18935
  };
18936
+ var DEFAULT_TEMP_SCRIPT_DIR = "/tmp";
18937
+ function buildDefaultSessionGuidance(tempScriptDir) {
18938
+ return [
18939
+ "Claude Warden is active. It filters Bash commands against safety rules and may ask or deny.",
18940
+ "",
18941
+ "- For JSON in shell pipelines, prefer `jq` (auto-allowed) over `python3 -c` / `node -e`.",
18942
+ `- For multi-line logic, save a temp script under \`${tempScriptDir}/\` (e.g. \`${tempScriptDir}/warden-task.sh\`) or add a \`package.json\` script rather than inline \`bash -c\` / \`node -e\`. Avoid polluting the repo with throwaway scripts.`,
18943
+ "- When Warden denies or asks, read the reason \u2014 it often names the preferred alternative.",
18944
+ "- To permanently allow a specific command, run `/warden:allow <cmd>`. To temporarily bypass filtering, `/warden:yolo`."
18945
+ ].join("\n");
18946
+ }
18947
+ var DEFAULT_SESSION_GUIDANCE = buildDefaultSessionGuidance(DEFAULT_TEMP_SCRIPT_DIR);
18936
18948
  var DEFAULT_CONFIG = {
18937
18949
  defaultDecision: "ask",
18938
18950
  askOnSubshell: true,
@@ -19806,6 +19818,18 @@ function mergeNonLayerFields(config, raw) {
19806
19818
  if (typeof raw.notifyOnDeny === "boolean") {
19807
19819
  config.notifyOnDeny = raw.notifyOnDeny;
19808
19820
  }
19821
+ if (typeof raw.sessionGuidance === "string" || raw.sessionGuidance === false) {
19822
+ config.sessionGuidance = raw.sessionGuidance;
19823
+ } else if (raw.sessionGuidance !== void 0) {
19824
+ warn(`[warden] Warning: invalid sessionGuidance (expected string or false), ignoring
19825
+ `);
19826
+ }
19827
+ if (typeof raw.tempScriptDir === "string" && raw.tempScriptDir.length > 0) {
19828
+ config.tempScriptDir = raw.tempScriptDir;
19829
+ } else if (raw.tempScriptDir !== void 0) {
19830
+ warn(`[warden] Warning: invalid tempScriptDir (expected non-empty string), ignoring
19831
+ `);
19832
+ }
19809
19833
  if (raw.skills && typeof raw.skills === "object") {
19810
19834
  const skills = raw.skills;
19811
19835
  if (typeof skills.defaultDecision === "string") {
@@ -20990,6 +21014,18 @@ function emitDecision(decision, reason, stderrMessage) {
20990
21014
  }
20991
21015
  process.exit(0);
20992
21016
  }
21017
+ function handleSessionStart(config) {
21018
+ if (config.sessionGuidance === false) process.exit(0);
21019
+ const text = config.sessionGuidance ?? buildDefaultSessionGuidance(config.tempScriptDir ?? DEFAULT_TEMP_SCRIPT_DIR);
21020
+ const output = {
21021
+ hookSpecificOutput: {
21022
+ hookEventName: "SessionStart",
21023
+ additionalContext: text
21024
+ }
21025
+ };
21026
+ process.stdout.write(JSON.stringify(output));
21027
+ process.exit(0);
21028
+ }
20993
21029
  function handleYoloMode(sessionId, result) {
20994
21030
  const yoloState = getYoloState(sessionId);
20995
21031
  if (!yoloState) return;
@@ -21011,6 +21047,10 @@ async function main() {
21011
21047
  } catch {
21012
21048
  process.exit(0);
21013
21049
  }
21050
+ if (input.hook_event_name === "SessionStart") {
21051
+ const config2 = loadConfig(input.cwd);
21052
+ handleSessionStart(config2);
21053
+ }
21014
21054
  if (input.tool_name !== "Bash" && input.tool_name !== "Skill") {
21015
21055
  process.exit(0);
21016
21056
  }
package/hooks/hooks.json CHANGED
@@ -22,6 +22,17 @@
22
22
  }
23
23
  ]
24
24
  }
25
+ ],
26
+ "SessionStart": [
27
+ {
28
+ "hooks": [
29
+ {
30
+ "type": "command",
31
+ "command": "${CLAUDE_PLUGIN_ROOT}/dist/index.cjs",
32
+ "timeout": 5
33
+ }
34
+ ]
35
+ }
25
36
  ]
26
37
  }
27
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-warden",
3
- "version": "2.7.0",
3
+ "version": "2.8.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",