Intercepts destructive rm/unlink Bash and rewrites to move into .claude/deletions-bin.

Intercepts destructive rm/unlink Bash and rewrites to move into .claude/deletions-bin.
claude plugin marketplace add kenhuangus/claude-code-mods-pack
claude plugin install deletions-bin@kenhuang-modshooks/deletions-bin.mjs 28 lines1const LOG = { plugin: "deletions-bin", key: "log" };
2const DANGER = /^\s*(rm\s+(-[rRf]+\s+)*.+|unlink\s+|del\s+)/i;
3export function register(on) {
4 on("session.start", async ($, e, next) => {
5 const r = await next(e);
6 await $.state.set(LOG, { count: 0 });
7 return r;
8 });
9 on("tool.call", { tool: "Bash" }, async ($, e, next) => {
10 const cmd = String(e.command ?? "");
11 if (!DANGER.test(cmd)) return next(e);
12 // Soft-guard: rewrite rm -rf path -> mkdir bin && mv path bin/
13 const m = cmd.match(/\brm\s+(?:-[a-zA-Z]+\s+)*(.+)$/);
14 const target = (m?.[1] || "UNKNOWN").trim();
15 const safe = `mkdir -p .claude/deletions-bin && mv -- ${target} .claude/deletions-bin/ 2>/dev/null || mv ${target} .claude/deletions-bin/`;
16 const { value: log } = await $.state.get(LOG);
17 await $.state.set(LOG, { count: (log?.count ?? 0) + 1, last: target });
18 return next({ ...e, command: safe });
19 });
20 on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
21 if (e.props?.hasSurvey) return next(e);
22 const { value: log } = await $.state.get(LOG);
23 if (!log?.count) return next(e);
24 const { Box, Text } = $.ui.resolve(e);
25 return Box({ paddingX: 1, children: [Text({ color: "yellow", bold: true, children: `DELETIONS-BIN rescued:${log.count}` })] });
26 });
27}
28types/index.d.ts 8 lines1declare module "claude-code" {
2 interface PluginState {
3 "deletions-bin": {
4 log: { count: number; last?: string };
5 };
6 }
7}
8