block-no-verify 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -1,24 +1,42 @@
1
1
  # block-no-verify
2
2
 
3
- A security tool that blocks the `--no-verify` flag in git commands. Designed to prevent AI agents from bypassing git hooks.
3
+ A platform-agnostic security tool that blocks the `--no-verify` flag in git commands. Designed to prevent AI agents from bypassing git hooks.
4
4
 
5
5
  ## Why?
6
6
 
7
- When using AI coding assistants like Claude Code, you might have git hooks (pre-commit, pre-push) that enforce code quality, run tests, or perform security checks. The `--no-verify` flag allows bypassing these hooks, which could allow AI agents to skip important validations.
7
+ When using AI coding assistants like Claude Code, Gemini CLI, Cursor, or others, you might have git hooks (pre-commit, pre-push) that enforce code quality, run tests, or perform security checks. The `--no-verify` flag allows bypassing these hooks, which could allow AI agents to skip important validations.
8
8
 
9
- This package provides a CLI that can be used as a Claude Code hook to block any git commands that include `--no-verify`.
9
+ This package provides a CLI that can block any git commands that include `--no-verify`, working with any AI tool that supports command hooks.
10
10
 
11
11
  ## Installation
12
12
 
13
13
  ```bash
14
- npm install -g block-no-verify
15
- # or
16
14
  pnpm add -g block-no-verify
17
15
  ```
18
16
 
19
- ## Usage with Claude Code
17
+ Or use without installation via `pnpm dlx block-no-verify` or `npx block-no-verify`.
20
18
 
21
- Add this to your `.claude/settings.json`:
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ # Check a command directly
23
+ block-no-verify "git commit --no-verify -m 'test'"
24
+ # Exit code: 2 (blocked)
25
+
26
+ # Check a safe command
27
+ block-no-verify "git commit -m 'test'"
28
+ # Exit code: 0 (allowed)
29
+
30
+ # Pipe from stdin
31
+ echo "git push --no-verify" | block-no-verify
32
+ # Exit code: 2 (blocked)
33
+ ```
34
+
35
+ ## Platform Integration
36
+
37
+ ### Claude Code
38
+
39
+ Add to your `.claude/settings.json`:
22
40
 
23
41
  ```json
24
42
  {
@@ -29,7 +47,7 @@ Add this to your `.claude/settings.json`:
29
47
  "hooks": [
30
48
  {
31
49
  "type": "command",
32
- "command": "block-no-verify"
50
+ "command": "pnpm dlx block-no-verify"
33
51
  }
34
52
  ]
35
53
  }
@@ -38,7 +56,89 @@ Add this to your `.claude/settings.json`:
38
56
  }
39
57
  ```
40
58
 
41
- This will block any Bash commands that contain `--no-verify` with supported git commands.
59
+ ### Gemini CLI
60
+
61
+ Gemini CLI supports hooks via `.gemini/settings.json`. The hook system mirrors Claude Code's JSON-over-stdin contract and exit code semantics.
62
+
63
+ Add to your `.gemini/settings.json`:
64
+
65
+ ```json
66
+ {
67
+ "hooks": {
68
+ "BeforeTool": [
69
+ {
70
+ "matcher": "run_shell_command",
71
+ "hooks": [
72
+ {
73
+ "name": "block-no-verify",
74
+ "type": "command",
75
+ "command": "pnpm dlx block-no-verify",
76
+ "description": "Block --no-verify flags in git commands",
77
+ "timeout": 5000
78
+ }
79
+ ]
80
+ }
81
+ ]
82
+ }
83
+ }
84
+ ```
85
+
86
+ > **Note:** Hooks are disabled by default in Gemini CLI. You may need to enable them in your settings. See [Gemini CLI Hooks Documentation](https://geminicli.com/docs/hooks/) for details.
87
+
88
+ ### Cursor
89
+
90
+ Cursor 1.7+ supports hooks via `.cursor/hooks.json`. The `beforeShellExecution` hook runs before any shell command.
91
+
92
+ Create `.cursor/hooks.json` in your project root:
93
+
94
+ ```json
95
+ {
96
+ "version": 1,
97
+ "hooks": {
98
+ "beforeShellExecution": [
99
+ {
100
+ "command": "pnpm dlx block-no-verify --format plain"
101
+ }
102
+ ]
103
+ }
104
+ }
105
+ ```
106
+
107
+ > **Note:** Cursor hooks are in beta. See [Cursor Hooks Documentation](https://cursor.com/docs/agent/hooks) for the latest information.
108
+
109
+ ### Generic Integration
110
+
111
+ block-no-verify accepts input in multiple formats:
112
+
113
+ ```bash
114
+ # Plain text (default)
115
+ block-no-verify "git commit --no-verify"
116
+
117
+ # JSON with command field
118
+ echo '{"command":"git commit --no-verify"}' | block-no-verify
119
+
120
+ # JSON with other fields (cmd, input, shell, script)
121
+ echo '{"cmd":"git push --no-verify"}' | block-no-verify
122
+
123
+ # Claude Code format (auto-detected)
124
+ echo '{"tool_input":{"command":"git commit --no-verify"}}' | block-no-verify
125
+ ```
126
+
127
+ ## CLI Options
128
+
129
+ ```
130
+ block-no-verify [options] [command]
131
+
132
+ Options:
133
+ --format <type> Input format: auto, plain, claude-code, json (default: auto)
134
+ --help, -h Show help message
135
+ --version, -v Show version
136
+
137
+ Input Methods:
138
+ 1. Command argument: block-no-verify "git commit --no-verify"
139
+ 2. Stdin (plain): echo "git commit --no-verify" | block-no-verify
140
+ 3. Stdin (JSON): echo '{"command":"..."}' | block-no-verify
141
+ ```
42
142
 
43
143
  ## Supported Git Commands
44
144
 
@@ -69,51 +169,29 @@ The following git commands are monitored for `--no-verify`:
69
169
  - `2` - Command is blocked (contains `--no-verify`)
70
170
  - `1` - An error occurred
71
171
 
72
- ## Programmatic Usage
73
-
74
- ```typescript
75
- import {
76
- checkCommand,
77
- detectGitCommand,
78
- hasNoVerifyFlag,
79
- } from 'block-no-verify'
80
-
81
- // Check if a command should be blocked
82
- const result = checkCommand('git commit --no-verify -m "test"')
83
- console.log(result.blocked) // true
84
- console.log(result.reason) // "BLOCKED: --no-verify flag is not allowed..."
85
-
86
- // Detect git command type
87
- const cmd = detectGitCommand('git push origin main')
88
- console.log(cmd) // "push"
89
-
90
- // Check for no-verify flag
91
- const hasFlag = hasNoVerifyFlag('git commit -n -m "test"', 'commit')
92
- console.log(hasFlag) // true
93
- ```
94
-
95
- ## Development
96
-
97
- ```bash
98
- # Install dependencies
99
- pnpm install
172
+ ## Supported JSON Fields
100
173
 
101
- # Build
102
- pnpm build
174
+ When using JSON input (auto-detected or with `--format json`), the following fields are recognized:
103
175
 
104
- # Run tests
105
- pnpm test
176
+ | Field | Description |
177
+ | -------------------- | ------------------------- |
178
+ | `tool_input.command` | Claude Code format |
179
+ | `command` | Generic command field |
180
+ | `cmd` | Alternative command field |
181
+ | `input` | Input field |
182
+ | `shell` | Shell command field |
183
+ | `script` | Script field |
106
184
 
107
- # Run tests with coverage
108
- pnpm test:coverage
185
+ ## Contributing
109
186
 
110
- # Lint
111
- pnpm lint
112
-
113
- # Format
114
- pnpm format
115
- ```
187
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
116
188
 
117
189
  ## License
118
190
 
119
191
  MIT
192
+
193
+ ## References
194
+
195
+ - [Claude Code Hooks](https://docs.anthropic.com/en/docs/claude-code)
196
+ - [Gemini CLI Hooks](https://geminicli.com/docs/hooks/)
197
+ - [Cursor Hooks](https://cursor.com/docs/agent/hooks)
@@ -0,0 +1,11 @@
1
+ import type { InputFormat } from './input-format.js';
2
+ /**
3
+ * Parsed CLI arguments
4
+ */
5
+ export interface CliArgs {
6
+ format: InputFormat;
7
+ command: string | null;
8
+ showHelp: boolean;
9
+ showVersion: boolean;
10
+ }
11
+ //# sourceMappingURL=cli-args-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args-result.d.ts","sourceRoot":"","sources":["../src/cli-args-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,OAAO,CAAA;CACrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-args-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args-result.js","sourceRoot":"","sources":["../src/cli-args-result.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { CliArgs } from './cli-args-result.js';
2
+ /**
3
+ * Parse CLI arguments
4
+ */
5
+ export declare function parseArgs(args: string[], onError: (message: string) => never): CliArgs;
6
+ //# sourceMappingURL=cli-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.d.ts","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAsBnD;;GAEG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,GAClC,OAAO,CA+DT"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Valid format values
3
+ */
4
+ const VALID_FORMATS = ['auto', 'plain', 'claude-code', 'json'];
5
+ /**
6
+ * Check if a value is a valid format
7
+ */
8
+ function isValidFormat(value) {
9
+ return VALID_FORMATS.includes(value);
10
+ }
11
+ /**
12
+ * Get argument at index safely using Array.at()
13
+ */
14
+ function getArgAt(args, index) {
15
+ return args.at(index);
16
+ }
17
+ /**
18
+ * Parse CLI arguments
19
+ */
20
+ export function parseArgs(args, onError) {
21
+ const result = {
22
+ format: 'auto',
23
+ command: null,
24
+ showHelp: false,
25
+ showVersion: false,
26
+ };
27
+ let i = 0;
28
+ while (i < args.length) {
29
+ const arg = getArgAt(args, i);
30
+ if (arg === undefined) {
31
+ i++;
32
+ continue;
33
+ }
34
+ if (arg === '--help' || arg === '-h') {
35
+ result.showHelp = true;
36
+ i++;
37
+ continue;
38
+ }
39
+ if (arg === '--version' || arg === '-v') {
40
+ result.showVersion = true;
41
+ i++;
42
+ continue;
43
+ }
44
+ if (arg === '--format') {
45
+ i++;
46
+ const formatArg = getArgAt(args, i);
47
+ if (formatArg === undefined) {
48
+ return onError('Missing format value');
49
+ }
50
+ if (!isValidFormat(formatArg)) {
51
+ return onError(`Invalid format: ${formatArg}`);
52
+ }
53
+ result.format = formatArg;
54
+ i++;
55
+ continue;
56
+ }
57
+ if (arg.startsWith('--format=')) {
58
+ const formatArg = arg.slice('--format='.length);
59
+ if (!isValidFormat(formatArg)) {
60
+ return onError(`Invalid format: ${formatArg}`);
61
+ }
62
+ result.format = formatArg;
63
+ i++;
64
+ continue;
65
+ }
66
+ if (arg.startsWith('-')) {
67
+ return onError(`Unknown option: ${arg}`);
68
+ }
69
+ // Positional argument = command
70
+ result.command = arg;
71
+ i++;
72
+ }
73
+ return result;
74
+ }
75
+ //# sourceMappingURL=cli-args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.js","sourceRoot":"","sources":["../src/cli-args.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,CAAC,CAAA;AAE9D;;GAEG;AACH,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;AACtC,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,IAAc,EAAE,KAAa;IAC7C,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAA;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CACvB,IAAc,EACd,OAAmC;IAEnC,MAAM,MAAM,GAAY;QACtB,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,IAAI;QACb,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,KAAK;KACnB,CAAA;IAED,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;QAE7B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACrC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAA;YACtB,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAA;YACzB,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;YACvB,CAAC,EAAE,CAAA;YACH,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;YACnC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,OAAO,OAAO,CAAC,sBAAsB,CAAC,CAAA;YACxC,CAAC;YACD,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9B,OAAO,OAAO,CAAC,mBAAmB,SAAS,EAAE,CAAC,CAAA;YAChD,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,SAAS,CAAA;YACzB,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,IAAI,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;YAC/C,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9B,OAAO,OAAO,CAAC,mBAAmB,SAAS,EAAE,CAAC,CAAA;YAChD,CAAC;YACD,MAAM,CAAC,MAAM,GAAG,SAAS,CAAA;YACzB,CAAC,EAAE,CAAA;YACH,SAAQ;QACV,CAAC;QAED,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,OAAO,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAA;QAC1C,CAAC;QAED,gCAAgC;QAChC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAA;QACpB,CAAC,EAAE,CAAA;IACL,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * CLI help text
3
+ */
4
+ export declare const HELP_TEXT = "block-no-verify - Block --no-verify flags in git commands\n\nUSAGE:\n block-no-verify [options] [command]\n\nOPTIONS:\n --format <type> Input format: auto, plain, claude-code, json (default: auto)\n --help, -h Show this help message\n --version, -v Show version number\n\nINPUT METHODS:\n 1. Command argument:\n block-no-verify \"git commit --no-verify -m 'test'\"\n\n 2. Stdin (plain text):\n echo \"git commit --no-verify\" | block-no-verify\n\n 3. Stdin (JSON - auto-detected):\n echo '{\"command\":\"git commit --no-verify\"}' | block-no-verify\n\n 4. Stdin (Claude Code format):\n echo '{\"tool_input\":{\"command\":\"git commit\"}}' | block-no-verify --format claude-code\n\nSUPPORTED JSON FIELDS:\n When using JSON input, the following fields are recognized:\n - tool_input.command (Claude Code format)\n - command\n - cmd\n - input\n - shell\n - script\n\nEXIT CODES:\n 0 - Command is allowed\n 2 - Command is blocked (contains --no-verify)\n 1 - An error occurred\n\nEXAMPLES:\n # Claude Code hook (.claude/settings.json)\n {\n \"hooks\": {\n \"PreToolUse\": [{\n \"matcher\": \"Bash\",\n \"hooks\": [{ \"type\": \"command\", \"command\": \"block-no-verify\" }]\n }]\n }\n }\n\n # Generic AI tool integration\n block-no-verify --format plain \"git push --no-verify\"\n\n # Pipe from another command\n your-ai-tool get-command | block-no-verify";
5
+ //# sourceMappingURL=cli-help.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-help.d.ts","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,SAAS,k6CAoDuB,CAAA"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * CLI help text
3
+ */
4
+ export const HELP_TEXT = `block-no-verify - Block --no-verify flags in git commands
5
+
6
+ USAGE:
7
+ block-no-verify [options] [command]
8
+
9
+ OPTIONS:
10
+ --format <type> Input format: auto, plain, claude-code, json (default: auto)
11
+ --help, -h Show this help message
12
+ --version, -v Show version number
13
+
14
+ INPUT METHODS:
15
+ 1. Command argument:
16
+ block-no-verify "git commit --no-verify -m 'test'"
17
+
18
+ 2. Stdin (plain text):
19
+ echo "git commit --no-verify" | block-no-verify
20
+
21
+ 3. Stdin (JSON - auto-detected):
22
+ echo '{"command":"git commit --no-verify"}' | block-no-verify
23
+
24
+ 4. Stdin (Claude Code format):
25
+ echo '{"tool_input":{"command":"git commit"}}' | block-no-verify --format claude-code
26
+
27
+ SUPPORTED JSON FIELDS:
28
+ When using JSON input, the following fields are recognized:
29
+ - tool_input.command (Claude Code format)
30
+ - command
31
+ - cmd
32
+ - input
33
+ - shell
34
+ - script
35
+
36
+ EXIT CODES:
37
+ 0 - Command is allowed
38
+ 2 - Command is blocked (contains --no-verify)
39
+ 1 - An error occurred
40
+
41
+ EXAMPLES:
42
+ # Claude Code hook (.claude/settings.json)
43
+ {
44
+ "hooks": {
45
+ "PreToolUse": [{
46
+ "matcher": "Bash",
47
+ "hooks": [{ "type": "command", "command": "block-no-verify" }]
48
+ }]
49
+ }
50
+ }
51
+
52
+ # Generic AI tool integration
53
+ block-no-verify --format plain "git push --no-verify"
54
+
55
+ # Pipe from another command
56
+ your-ai-tool get-command | block-no-verify`;
57
+ //# sourceMappingURL=cli-help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-help.js","sourceRoot":"","sources":["../src/cli-help.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6CAoDoB,CAAA"}
package/dist/cli.d.ts CHANGED
@@ -2,31 +2,8 @@
2
2
  /**
3
3
  * block-no-verify CLI
4
4
  *
5
- * Usage in Claude Code hooks:
6
- *
7
- * In your .claude/settings.json:
8
- * ```json
9
- * {
10
- * "hooks": {
11
- * "PreToolUse": [
12
- * {
13
- * "matcher": "Bash",
14
- * "hooks": [
15
- * {
16
- * "type": "command",
17
- * "command": "block-no-verify"
18
- * }
19
- * ]
20
- * }
21
- * ]
22
- * }
23
- * }
24
- * ```
25
- *
26
- * The CLI reads the command from stdin and exits with:
27
- * - 0 if the command is allowed
28
- * - 2 if the command is blocked (contains --no-verify)
29
- * - 1 if an error occurred
5
+ * A platform-agnostic tool to block --no-verify flags in git commands.
6
+ * Works with Claude Code, Gemini CLI, Cursor, and other AI coding tools.
30
7
  */
31
8
  export {};
32
9
  //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/cli.js CHANGED
@@ -2,64 +2,62 @@
2
2
  /**
3
3
  * block-no-verify CLI
4
4
  *
5
- * Usage in Claude Code hooks:
6
- *
7
- * In your .claude/settings.json:
8
- * ```json
9
- * {
10
- * "hooks": {
11
- * "PreToolUse": [
12
- * {
13
- * "matcher": "Bash",
14
- * "hooks": [
15
- * {
16
- * "type": "command",
17
- * "command": "block-no-verify"
18
- * }
19
- * ]
20
- * }
21
- * ]
22
- * }
23
- * }
24
- * ```
25
- *
26
- * The CLI reads the command from stdin and exits with:
27
- * - 0 if the command is allowed
28
- * - 2 if the command is blocked (contains --no-verify)
29
- * - 1 if an error occurred
5
+ * A platform-agnostic tool to block --no-verify flags in git commands.
6
+ * Works with Claude Code, Gemini CLI, Cursor, and other AI coding tools.
30
7
  */
8
+ import { parseArgs } from './cli-args.js';
9
+ import { HELP_TEXT } from './cli-help.js';
10
+ import { parseInput } from './parse-input.js';
31
11
  import { checkCommand, EXIT_CODES } from './index.js';
12
+ const VERSION = '1.1.0';
32
13
  async function readStdin() {
33
14
  return new Promise((resolve, reject) => {
34
15
  let data = '';
35
- // Set encoding to utf8
36
16
  process.stdin.setEncoding('utf8');
37
- // Handle data chunks
38
17
  process.stdin.on('data', chunk => {
39
18
  data += chunk;
40
19
  });
41
- // Resolve when stdin ends
42
20
  process.stdin.on('end', () => {
43
21
  resolve(data);
44
22
  });
45
- // Handle errors
46
23
  process.stdin.on('error', err => {
47
24
  reject(err);
48
25
  });
49
- // If stdin is empty/not piped, resolve with empty string after a short timeout
50
26
  if (process.stdin.isTTY) {
51
27
  resolve('');
52
28
  }
53
29
  });
54
30
  }
31
+ function handleError(message) {
32
+ console.error(message);
33
+ console.error('Valid formats: auto, plain, claude-code, json');
34
+ console.error('Use --help for usage information');
35
+ process.exit(EXIT_CODES.ERROR);
36
+ }
55
37
  async function main() {
56
38
  try {
57
- const input = await readStdin();
58
- if (!input.trim()) {
59
- // No input provided, allow by default
39
+ const args = parseArgs(process.argv.slice(2), handleError);
40
+ if (args.showHelp) {
41
+ console.log(HELP_TEXT);
42
+ process.exit(EXIT_CODES.ALLOWED);
43
+ }
44
+ if (args.showVersion) {
45
+ console.log(VERSION);
46
+ process.exit(EXIT_CODES.ALLOWED);
47
+ }
48
+ // Get input from argument or stdin
49
+ let rawInput;
50
+ if (args.command !== null) {
51
+ rawInput = args.command;
52
+ }
53
+ else {
54
+ rawInput = await readStdin();
55
+ }
56
+ if (!rawInput.trim()) {
60
57
  process.exit(EXIT_CODES.ALLOWED);
61
58
  }
62
- const result = checkCommand(input);
59
+ const { command } = parseInput(rawInput, args.format);
60
+ const result = checkCommand(command);
63
61
  if (result.blocked) {
64
62
  console.error(result.reason);
65
63
  process.exit(EXIT_CODES.BLOCKED);
@@ -67,7 +65,8 @@ async function main() {
67
65
  process.exit(EXIT_CODES.ALLOWED);
68
66
  }
69
67
  catch (error) {
70
- console.error('Error:', error instanceof Error ? error.message : String(error));
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ console.error('Error:', message);
71
70
  process.exit(EXIT_CODES.ERROR);
72
71
  }
73
72
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAErD,KAAK,UAAU,SAAS;IACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAA;QAEb,uBAAuB;QACvB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAEjC,qBAAqB;QACrB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;YAC/B,IAAI,IAAI,KAAK,CAAA;QACf,CAAC,CAAC,CAAA;QAEF,0BAA0B;QAC1B,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YAC3B,OAAO,CAAC,IAAI,CAAC,CAAA;QACf,CAAC,CAAC,CAAA;QAEF,gBAAgB;QAChB,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE;YAC9B,MAAM,CAAC,GAAG,CAAC,CAAA;QACb,CAAC,CAAC,CAAA;QAEF,+EAA+E;QAC/E,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACxB,OAAO,CAAC,EAAE,CAAC,CAAA;QACb,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,SAAS,EAAE,CAAA;QAE/B,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAClB,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;QAClC,CAAC;QAED,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;QAElC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC5B,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;QAClC,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,QAAQ,EACR,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CACvD,CAAA;QACD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAA;IAChC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAA"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"detect-git-command.d.ts","sourceRoot":"","sources":["../src/detect-git-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAmCjE"}
1
+ {"version":3,"file":"detect-git-command.d.ts","sourceRoot":"","sources":["../src/detect-git-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAsClD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAuBjE"}
@@ -1,38 +1,67 @@
1
1
  import { GIT_COMMANDS_WITH_NO_VERIFY } from './types.js';
2
+ const VALID_BEFORE_GIT = ' \t\n\r;&|$`(<{!"\']/.~\\';
3
+ function isInComment(input, idx) {
4
+ const lineStart = input.lastIndexOf('\n', idx - 1) + 1;
5
+ const before = input.slice(lineStart, idx);
6
+ for (let i = 0; i < before.length; i++) {
7
+ if (before.charAt(i) === '#') {
8
+ const prev = i > 0 ? before.charAt(i - 1) : '';
9
+ if (prev !== '$' && prev !== '\\')
10
+ return true;
11
+ }
12
+ }
13
+ return false;
14
+ }
15
+ function findGit(input, start) {
16
+ let pos = start;
17
+ while (pos < input.length) {
18
+ const idx = input.indexOf('git', pos);
19
+ if (idx === -1)
20
+ return null;
21
+ const isExe = input.slice(idx + 3, idx + 7).toLowerCase() === '.exe';
22
+ const len = isExe ? 7 : 3;
23
+ const after = input[idx + len] || ' ';
24
+ if (!/[\s"']/.test(after)) {
25
+ pos = idx + 1;
26
+ continue;
27
+ }
28
+ const before = idx > 0 ? input[idx - 1] : ' ';
29
+ if (VALID_BEFORE_GIT.includes(before))
30
+ return { idx, len };
31
+ pos = idx + 1;
32
+ }
33
+ return null;
34
+ }
2
35
  /**
3
36
  * Checks if the input contains a git command
4
37
  */
5
38
  export function detectGitCommand(input) {
6
- for (const cmd of GIT_COMMANDS_WITH_NO_VERIFY) {
7
- // Match patterns like: git commit, git -C /path commit
8
- // Using string concatenation and simple includes/match to avoid non-literal RegExp
9
- const gitIndex = input.indexOf('git');
10
- if (gitIndex === -1)
11
- continue;
12
- const cmdIndex = input.indexOf(cmd, gitIndex);
13
- if (cmdIndex === -1)
14
- continue;
15
- // Verify 'git' is a word boundary (not part of another word)
16
- const beforeGit = gitIndex > 0 ? input[gitIndex - 1] : ' ';
17
- const afterGit = input[gitIndex + 3] || ' ';
18
- if (!/\s|^$/.test(beforeGit) && beforeGit !== ';' && beforeGit !== '&') {
39
+ let start = 0;
40
+ while (start < input.length) {
41
+ const git = findGit(input, start);
42
+ if (!git)
43
+ return null;
44
+ if (isInComment(input, git.idx)) {
45
+ start = git.idx + git.len;
19
46
  continue;
20
47
  }
21
- if (!/\s/.test(afterGit))
22
- continue;
23
- // Verify the command is a word boundary
24
- const beforeCmd = cmdIndex > 0 ? input[cmdIndex - 1] : ' ';
25
- const afterCmd = input[cmdIndex + cmd.length] || ' ';
26
- if (!/\s/.test(beforeCmd))
27
- continue;
28
- if (!/\s|$/.test(afterCmd) && afterCmd !== ';' && afterCmd !== '&') {
29
- continue;
48
+ for (const cmd of GIT_COMMANDS_WITH_NO_VERIFY) {
49
+ const cmdIdx = input.indexOf(cmd, git.idx + git.len);
50
+ if (cmdIdx === -1)
51
+ continue;
52
+ const before = cmdIdx > 0 ? input[cmdIdx - 1] : ' ';
53
+ const after = input[cmdIdx + cmd.length] || ' ';
54
+ if (!/\s/.test(before))
55
+ continue;
56
+ if (!/[\s;&#|>)\]}"']/.test(after) && after !== '')
57
+ continue;
58
+ if (/[;|]/.test(input.slice(git.idx + git.len, cmdIdx)))
59
+ continue;
60
+ if (isInComment(input, cmdIdx))
61
+ continue;
62
+ return cmd;
30
63
  }
31
- // Make sure there's no pipe or semicolon between git and cmd that would indicate separate commands
32
- const between = input.slice(gitIndex + 3, cmdIndex);
33
- if (/[;|]/.test(between))
34
- continue;
35
- return cmd;
64
+ start = git.idx + git.len;
36
65
  }
37
66
  return null;
38
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"detect-git-command.js","sourceRoot":"","sources":["../src/detect-git-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAGxD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,KAAK,MAAM,GAAG,IAAI,2BAA2B,EAAE,CAAC;QAC9C,uDAAuD;QACvD,mFAAmF;QACnF,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QACrC,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,SAAQ;QAE7B,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;QAC7C,IAAI,QAAQ,KAAK,CAAC,CAAC;YAAE,SAAQ;QAE7B,6DAA6D;QAC7D,MAAM,SAAS,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,GAAG,CAAA;QAE3C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,SAAS,KAAK,GAAG,IAAI,SAAS,KAAK,GAAG,EAAE,CAAC;YACvE,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,SAAQ;QAElC,wCAAwC;QACxC,MAAM,SAAS,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC1D,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAA;QAEpD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,SAAQ;QACnC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;YACnE,SAAQ;QACV,CAAC;QAED,mGAAmG;QACnG,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAA;QACnD,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,SAAQ;QAElC,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
1
+ {"version":3,"file":"detect-git-command.js","sourceRoot":"","sources":["../src/detect-git-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAGxD,MAAM,gBAAgB,GAAG,2BAA2B,CAAA;AAEpD,SAAS,WAAW,CAAC,KAAa,EAAE,GAAW;IAC7C,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,CAAC,CAAA;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAC9C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAA;QAChD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,OAAO,CACd,KAAa,EACb,KAAa;IAEb,IAAI,GAAG,GAAG,KAAK,CAAA;IACf,OAAO,GAAG,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;QACrC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAA;QACpE,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;QACzB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,CAAA;QACrC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1B,GAAG,GAAG,GAAG,GAAG,CAAC,CAAA;YACb,SAAQ;QACV,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC7C,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;QAC1D,GAAG,GAAG,GAAG,GAAG,CAAC,CAAA;IACf,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;QACjC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QACrB,IAAI,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAA;YACzB,SAAQ;QACV,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,2BAA2B,EAAE,CAAC;YAC9C,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,CAAA;YACpD,IAAI,MAAM,KAAK,CAAC,CAAC;gBAAE,SAAQ;YAC3B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;YACnD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAA;YAC/C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;gBAAE,SAAQ;YAChC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE;gBAAE,SAAQ;YAC5D,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAAE,SAAQ;YACjE,IAAI,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC;gBAAE,SAAQ;YACxC,OAAO,GAAG,CAAA;QACZ,CAAC;QACD,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAA;IAC3B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
package/dist/index.d.ts CHANGED
@@ -11,4 +11,7 @@ export { EXIT_CODES } from './exit-codes.js';
11
11
  export { detectGitCommand } from './detect-git-command.js';
12
12
  export { hasNoVerifyFlag } from './has-no-verify-flag.js';
13
13
  export { checkCommand } from './check-command.js';
14
+ export { parseInput } from './parse-input.js';
15
+ export type { InputFormat } from './input-format.js';
16
+ export type { ParseResult } from './parse-result.js';
14
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AACxD,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAClD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -9,4 +9,5 @@ export { EXIT_CODES } from './exit-codes.js';
9
9
  export { detectGitCommand } from './detect-git-command.js';
10
10
  export { hasNoVerifyFlag } from './has-no-verify-flag.js';
11
11
  export { checkCommand } from './check-command.js';
12
+ export { parseInput } from './parse-input.js';
12
13
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Input format types supported by block-no-verify
3
+ */
4
+ export type InputFormat = 'auto' | 'plain' | 'claude-code' | 'json';
5
+ //# sourceMappingURL=input-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-format.d.ts","sourceRoot":"","sources":["../src/input-format.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=input-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-format.js","sourceRoot":"","sources":["../src/input-format.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import type { InputFormat } from './input-format.js';
2
+ import type { ParseResult } from './parse-result.js';
3
+ /**
4
+ * Parses input according to the specified format
5
+ *
6
+ * @param input - Raw input string
7
+ * @param format - Input format to use (defaults to 'auto' if not provided)
8
+ * @returns ParseResult with extracted command and detected format
9
+ *
10
+ * @example
11
+ * // Plain text
12
+ * parseInput('git commit --no-verify')
13
+ * // => { command: 'git commit --no-verify', format: 'plain' }
14
+ *
15
+ * @example
16
+ * // Claude Code format
17
+ * parseInput('{"tool_input":{"command":"git commit --no-verify"}}', 'claude-code')
18
+ * // => { command: 'git commit --no-verify', format: 'claude-code' }
19
+ *
20
+ * @example
21
+ * // Auto-detect JSON
22
+ * parseInput('{"command":"git commit --no-verify"}')
23
+ * // => { command: 'git commit --no-verify', format: 'json' }
24
+ */
25
+ export declare function parseInput(input: string, format?: InputFormat): ParseResult;
26
+ //# sourceMappingURL=parse-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-input.d.ts","sourceRoot":"","sources":["../src/parse-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAyHpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,WAAW,CAkB3E"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Attempts to parse input as JSON and extract the command
3
+ */
4
+ function tryParseJson(input) {
5
+ try {
6
+ const parsed = JSON.parse(input);
7
+ if (typeof parsed !== 'object' || parsed === null) {
8
+ return null;
9
+ }
10
+ // Check for Claude Code format: { tool_input: { command: "..." } }
11
+ if (hasProperty(parsed, 'tool_input')) {
12
+ const toolInput = getProperty(parsed, 'tool_input');
13
+ if (typeof toolInput === 'object' && toolInput !== null) {
14
+ if (hasProperty(toolInput, 'command')) {
15
+ const command = getProperty(toolInput, 'command');
16
+ if (typeof command === 'string') {
17
+ return command;
18
+ }
19
+ }
20
+ }
21
+ }
22
+ // Generic JSON formats
23
+ const genericKeys = ['command', 'cmd', 'input', 'shell', 'script'];
24
+ for (const key of genericKeys) {
25
+ if (hasProperty(parsed, key)) {
26
+ const value = getProperty(parsed, key);
27
+ if (typeof value === 'string') {
28
+ return value;
29
+ }
30
+ }
31
+ }
32
+ // If JSON but no recognized command field, return null
33
+ return null;
34
+ }
35
+ catch {
36
+ // Not valid JSON
37
+ return null;
38
+ }
39
+ }
40
+ /**
41
+ * Type-safe property check
42
+ */
43
+ function hasProperty(obj, key) {
44
+ return Object.prototype.hasOwnProperty.call(obj, key);
45
+ }
46
+ /**
47
+ * Type-safe property access using Object.entries
48
+ */
49
+ function getProperty(obj, key) {
50
+ const entries = Object.entries(obj);
51
+ for (const entry of entries) {
52
+ if (entry[0] === key) {
53
+ return entry[1];
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+ /**
59
+ * Parses input in Claude Code format
60
+ */
61
+ function parseClaudeCode(input) {
62
+ try {
63
+ const parsed = JSON.parse(input);
64
+ if (typeof parsed !== 'object' || parsed === null) {
65
+ return input;
66
+ }
67
+ if (hasProperty(parsed, 'tool_input')) {
68
+ const toolInput = getProperty(parsed, 'tool_input');
69
+ if (typeof toolInput === 'object' && toolInput !== null) {
70
+ if (hasProperty(toolInput, 'command')) {
71
+ const command = getProperty(toolInput, 'command');
72
+ if (typeof command === 'string') {
73
+ return command;
74
+ }
75
+ }
76
+ }
77
+ }
78
+ return input;
79
+ }
80
+ catch {
81
+ // If not valid JSON, treat as plain text
82
+ return input;
83
+ }
84
+ }
85
+ /**
86
+ * Parses input in generic JSON format
87
+ */
88
+ function parseJson(input) {
89
+ const command = tryParseJson(input);
90
+ if (command !== null) {
91
+ return command;
92
+ }
93
+ return input;
94
+ }
95
+ /**
96
+ * Auto-detects format and parses input
97
+ */
98
+ function parseAuto(input) {
99
+ const trimmed = input.trim();
100
+ // Try JSON first if it looks like JSON
101
+ if (trimmed.startsWith('{')) {
102
+ const command = tryParseJson(trimmed);
103
+ if (command !== null) {
104
+ return { command, format: 'json' };
105
+ }
106
+ }
107
+ // Otherwise treat as plain text
108
+ return { command: input, format: 'plain' };
109
+ }
110
+ /**
111
+ * Parses input according to the specified format
112
+ *
113
+ * @param input - Raw input string
114
+ * @param format - Input format to use (defaults to 'auto' if not provided)
115
+ * @returns ParseResult with extracted command and detected format
116
+ *
117
+ * @example
118
+ * // Plain text
119
+ * parseInput('git commit --no-verify')
120
+ * // => { command: 'git commit --no-verify', format: 'plain' }
121
+ *
122
+ * @example
123
+ * // Claude Code format
124
+ * parseInput('{"tool_input":{"command":"git commit --no-verify"}}', 'claude-code')
125
+ * // => { command: 'git commit --no-verify', format: 'claude-code' }
126
+ *
127
+ * @example
128
+ * // Auto-detect JSON
129
+ * parseInput('{"command":"git commit --no-verify"}')
130
+ * // => { command: 'git commit --no-verify', format: 'json' }
131
+ */
132
+ export function parseInput(input, format) {
133
+ const resolvedFormat = format === undefined || format === null ? 'auto' : format;
134
+ if (resolvedFormat === 'plain') {
135
+ return { command: input, format: 'plain' };
136
+ }
137
+ if (resolvedFormat === 'claude-code') {
138
+ return { command: parseClaudeCode(input), format: 'claude-code' };
139
+ }
140
+ if (resolvedFormat === 'json') {
141
+ return { command: parseJson(input), format: 'json' };
142
+ }
143
+ // resolvedFormat === 'auto'
144
+ return parseAuto(input);
145
+ }
146
+ //# sourceMappingURL=parse-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-input.js","sourceRoot":"","sources":["../src/parse-input.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { InputFormat } from './input-format.js';
2
+ /**
3
+ * Result of parsing input
4
+ */
5
+ export interface ParseResult {
6
+ command: string;
7
+ format: InputFormat;
8
+ }
9
+ //# sourceMappingURL=parse-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-result.d.ts","sourceRoot":"","sources":["../src/parse-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,WAAW,CAAA;CACpB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parse-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-result.js","sourceRoot":"","sources":["../src/parse-result.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "block-no-verify",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI tool to block --no-verify flag in git commands. Prevents AI agents from bypassing git hooks.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",