@goobits/sherpa 1.0.1

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.
Files changed (55) hide show
  1. package/dist/chunk-3CILH2TO.js +387 -0
  2. package/dist/chunk-3CILH2TO.js.map +7 -0
  3. package/dist/chunk-5NF3BSD6.js +512 -0
  4. package/dist/chunk-5NF3BSD6.js.map +7 -0
  5. package/dist/chunk-IIU6U7TE.js +307 -0
  6. package/dist/chunk-IIU6U7TE.js.map +7 -0
  7. package/dist/chunk-LQZTKH3U.js +307 -0
  8. package/dist/chunk-LQZTKH3U.js.map +7 -0
  9. package/dist/cli.d.ts +11 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +84 -0
  12. package/dist/cli.js.map +7 -0
  13. package/dist/commands/init.d.ts +7 -0
  14. package/dist/commands/init.d.ts.map +1 -0
  15. package/dist/commands/init.js +333 -0
  16. package/dist/commands/init.js.map +1 -0
  17. package/dist/commands/post.d.ts +20 -0
  18. package/dist/commands/post.d.ts.map +1 -0
  19. package/dist/commands/post.js +183 -0
  20. package/dist/commands/post.js.map +1 -0
  21. package/dist/commands/pre.d.ts +18 -0
  22. package/dist/commands/pre.d.ts.map +1 -0
  23. package/dist/commands/pre.js +102 -0
  24. package/dist/commands/pre.js.map +1 -0
  25. package/dist/commands/status.d.ts +5 -0
  26. package/dist/commands/status.d.ts.map +1 -0
  27. package/dist/commands/status.js +48 -0
  28. package/dist/commands/status.js.map +1 -0
  29. package/dist/daemon-V2QDZTUB.js +89 -0
  30. package/dist/daemon-V2QDZTUB.js.map +7 -0
  31. package/dist/daemon.d.ts +9 -0
  32. package/dist/daemon.d.ts.map +1 -0
  33. package/dist/daemon.js +112 -0
  34. package/dist/daemon.js.map +1 -0
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +36 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/parser.d.ts +21 -0
  40. package/dist/parser.d.ts.map +1 -0
  41. package/dist/parser.js +152 -0
  42. package/dist/parser.js.map +1 -0
  43. package/dist/reviewer/index.js +544 -0
  44. package/dist/reviewer/index.js.map +7 -0
  45. package/dist/rules.d.ts +21 -0
  46. package/dist/rules.d.ts.map +1 -0
  47. package/dist/rules.js +165 -0
  48. package/dist/rules.js.map +1 -0
  49. package/dist/status-Q6Z4TFJZ.js +52 -0
  50. package/dist/status-Q6Z4TFJZ.js.map +7 -0
  51. package/dist/types.d.ts +69 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +13 -0
  54. package/dist/types.js.map +1 -0
  55. package/package.json +52 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * sherpa post - PostToolUse hook that offloads large outputs to scratch files
3
+ */
4
+ import { countTokens, loadConfig, readHookInput, writeHookOutput } from '@goobits/sherpa-core';
5
+ import { createHash } from 'crypto';
6
+ import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { DEFAULT_CONFIG } from '../types.js';
9
+ /**
10
+ * Load guard config from .claude/guard.json or fallback locations
11
+ */
12
+ export function loadGuardConfig() {
13
+ const searchPaths = [
14
+ join(process.cwd(), '.claude', 'guard.json'),
15
+ join(process.cwd(), 'guard.json')
16
+ ];
17
+ return loadConfig('guard.json', DEFAULT_CONFIG, searchPaths);
18
+ }
19
+ /**
20
+ * Clean up scratch files by age and size
21
+ */
22
+ function cleanupScratch(scratchDir, maxAgeMinutes, maxSizeMB) {
23
+ try {
24
+ if (!existsSync(scratchDir)) {
25
+ return;
26
+ }
27
+ const files = readdirSync(scratchDir);
28
+ const now = Date.now();
29
+ const maxAge = maxAgeMinutes * 60 * 1000;
30
+ const maxBytes = maxSizeMB * 1024 * 1024;
31
+ // Collect file info
32
+ const fileInfos = [];
33
+ let totalSize = 0;
34
+ for (const file of files) {
35
+ if (!file.startsWith('out_')) {
36
+ continue;
37
+ }
38
+ const filepath = join(scratchDir, file);
39
+ try {
40
+ const stat = statSync(filepath);
41
+ fileInfos.push({
42
+ path: filepath,
43
+ size: stat.size,
44
+ mtime: stat.mtimeMs
45
+ });
46
+ totalSize += stat.size;
47
+ }
48
+ catch {
49
+ // Ignore errors on individual files
50
+ }
51
+ }
52
+ // Delete files older than maxAge
53
+ for (const info of fileInfos) {
54
+ if (now - info.mtime > maxAge) {
55
+ try {
56
+ unlinkSync(info.path);
57
+ totalSize -= info.size;
58
+ }
59
+ catch {
60
+ // Ignore
61
+ }
62
+ }
63
+ }
64
+ // If still over size limit, delete oldest files (LRU)
65
+ if (totalSize > maxBytes) {
66
+ const remaining = fileInfos
67
+ .filter(f => existsSync(f.path))
68
+ .sort((a, b) => a.mtime - b.mtime); // Oldest first
69
+ for (const info of remaining) {
70
+ if (totalSize <= maxBytes) {
71
+ break;
72
+ }
73
+ try {
74
+ unlinkSync(info.path);
75
+ totalSize -= info.size;
76
+ }
77
+ catch {
78
+ // Ignore
79
+ }
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ // Ignore if directory doesn't exist yet
85
+ }
86
+ }
87
+ /**
88
+ * Generate a short hash for the output
89
+ */
90
+ function hashOutput(content) {
91
+ return createHash('md5').update(content).digest('hex').slice(0, 8);
92
+ }
93
+ /**
94
+ * Offload large output to a scratch file
95
+ */
96
+ export function offloadOutput(output, exitCode, config) {
97
+ const tokens = countTokens(output);
98
+ const lines = output.split('\n');
99
+ // Small output: pass through
100
+ if (tokens <= config.maxTokens) {
101
+ return { modified: false, result: output };
102
+ }
103
+ // Create scratch directory
104
+ const scratchDir = join(process.cwd(), config.scratchDir);
105
+ mkdirSync(scratchDir, { recursive: true });
106
+ // Clean up old files and enforce size limit
107
+ cleanupScratch(scratchDir, config.maxAgeMinutes, config.maxScratchSizeMB);
108
+ // Save to scratch file
109
+ const hash = hashOutput(output);
110
+ const filename = `out_${hash}_exit${exitCode}.txt`;
111
+ const filepath = join(scratchDir, filename);
112
+ writeFileSync(filepath, output);
113
+ // Create preview (last N tokens worth of lines)
114
+ // Estimate ~4 chars per token to avoid expensive per-line token counting
115
+ const charsPerToken = 4;
116
+ const targetChars = config.previewTokens * charsPerToken;
117
+ const previewLines = [];
118
+ let charCount = 0;
119
+ for (let i = lines.length - 1; i >= 0 && charCount < targetChars; i--) {
120
+ previewLines.unshift(lines[i]);
121
+ charCount += lines[i].length + 1; // +1 for newline
122
+ }
123
+ const preview = previewLines.join('\n');
124
+ // Build pointer message
125
+ const sizeKB = (output.length / 1024).toFixed(1);
126
+ const result = [
127
+ `┌─ Output offloaded (${lines.length} lines, ${sizeKB}KB, ~${tokens} tokens)`,
128
+ `│ File: ${filepath}`,
129
+ `│ Hint: grep <pattern> ${filepath}`,
130
+ `└─ Last ${previewLines.length} lines:`,
131
+ preview
132
+ ].join('\n');
133
+ return { modified: true, result };
134
+ }
135
+ /**
136
+ * Main entry point for PostToolUse hook
137
+ */
138
+ export function runPost() {
139
+ try {
140
+ const data = readHookInput();
141
+ // Only handle Bash tool
142
+ if (data.tool_name !== 'Bash') {
143
+ writeHookOutput(data);
144
+ return;
145
+ }
146
+ const stdout = data.tool_result?.stdout || '';
147
+ const stderr = data.tool_result?.stderr || '';
148
+ const exitCode = data.tool_result?.exit_code || 0;
149
+ // Load config from .claude/guard.json or defaults
150
+ const config = loadGuardConfig();
151
+ // Check stdout
152
+ const stdoutResult = offloadOutput(stdout, exitCode, config);
153
+ // Check stderr (usually smaller, but handle anyway)
154
+ const stderrResult = offloadOutput(stderr, exitCode, config);
155
+ // If nothing was modified, pass through
156
+ if (!stdoutResult.modified && !stderrResult.modified) {
157
+ writeHookOutput(data);
158
+ return;
159
+ }
160
+ // Return modified result
161
+ const result = {
162
+ ...data,
163
+ tool_result: {
164
+ ...data.tool_result,
165
+ stdout: stdoutResult.result,
166
+ stderr: stderrResult.modified ? stderrResult.result : stderr
167
+ }
168
+ };
169
+ writeHookOutput(result);
170
+ }
171
+ catch (error) {
172
+ // On error, try to pass through original
173
+ console.error('sherpa post error:', error.message);
174
+ try {
175
+ const data = readHookInput();
176
+ writeHookOutput(data);
177
+ }
178
+ catch {
179
+ // Nothing we can do
180
+ }
181
+ }
182
+ }
183
+ //# sourceMappingURL=post.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post.js","sourceRoot":"","sources":["../../src/commands/post.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACN,WAAW,EACX,UAAU,EAEV,aAAa,EACb,eAAe,EACf,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,UAAU,EAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AAC3F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,EAAE,cAAc,EAAoB,MAAM,aAAa,CAAA;AAE9D;;GAEG;AACH,MAAM,UAAU,eAAe;IAC9B,MAAM,WAAW,GAAG;QACnB,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,YAAY,CAAC;QAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;KACjC,CAAA;IACD,OAAO,UAAU,CAAc,YAAY,EAAE,cAAc,EAAE,WAAW,CAAC,CAAA;AAC1E,CAAC;AAQD;;GAEG;AACH,SAAS,cAAc,CAAC,UAAkB,EAAE,aAAqB,EAAE,SAAiB;IACnF,IAAI,CAAC;QACJ,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAAA,OAAM;QAAA,CAAC;QAErC,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAA;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,MAAM,GAAG,aAAa,GAAG,EAAE,GAAG,IAAI,CAAA;QACxC,MAAM,QAAQ,GAAG,SAAS,GAAG,IAAI,GAAG,IAAI,CAAA;QAExC,oBAAoB;QACpB,MAAM,SAAS,GAAe,EAAE,CAAA;QAChC,IAAI,SAAS,GAAG,CAAC,CAAA;QAEjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAAA,SAAQ;YAAA,CAAC;YAExC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAA;YACvC,IAAI,CAAC;gBACJ,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAA;gBAC/B,SAAS,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,IAAI,CAAC,OAAO;iBACnB,CAAC,CAAA;gBACF,SAAS,IAAI,IAAI,CAAC,IAAI,CAAA;YACvB,CAAC;YAAC,MAAM,CAAC;gBACR,oCAAoC;YACrC,CAAC;QACF,CAAC;QAED,iCAAiC;QACjC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;YAC9B,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACJ,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACrB,SAAS,IAAI,IAAI,CAAC,IAAI,CAAA;gBACvB,CAAC;gBAAC,MAAM,CAAC;oBACR,SAAS;gBACV,CAAC;YACF,CAAC;QACF,CAAC;QAED,sDAAsD;QACtD,IAAI,SAAS,GAAG,QAAQ,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAG,SAAS;iBACzB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBAC/B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAA,CAAC,eAAe;YAEnD,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;gBAC9B,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;oBAAA,MAAK;gBAAA,CAAC;gBAClC,IAAI,CAAC;oBACJ,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACrB,SAAS,IAAI,IAAI,CAAC,IAAI,CAAA;gBACvB,CAAC;gBAAC,MAAM,CAAC;oBACR,SAAS;gBACV,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,wCAAwC;IACzC,CAAC;AACF,CAAC;AAED;;GAEG;AACH,SAAS,UAAU,CAAC,OAAe;IAClC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACnE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAC5B,MAAc,EACd,QAAgB,EAChB,MAAmB;IAEnB,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IAEhC,6BAA6B;IAC7B,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IAC3C,CAAC;IAED,2BAA2B;IAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IACzD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAE1C,4CAA4C;IAC5C,cAAc,CAAC,UAAU,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAEzE,uBAAuB;IACvB,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;IAC/B,MAAM,QAAQ,GAAG,OAAQ,IAAK,QAAS,QAAS,MAAM,CAAA;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAC3C,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IAE/B,gDAAgD;IAChD,yEAAyE;IACzE,MAAM,aAAa,GAAG,CAAC,CAAA;IACvB,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,GAAG,aAAa,CAAA;IACxD,MAAM,YAAY,GAAa,EAAE,CAAA;IACjC,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,SAAS,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;QACvE,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9B,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA,CAAC,iBAAiB;IACnD,CAAC;IACD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEvC,wBAAwB;IACxB,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG;QACd,wBAAyB,KAAK,CAAC,MAAO,WAAY,MAAO,QAAS,MAAO,UAAU;QACnF,WAAY,QAAS,EAAE;QACvB,0BAA2B,QAAS,EAAE;QACtC,WAAY,YAAY,CAAC,MAAO,SAAS;QACzC,OAAO;KACP,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEZ,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO;IACtB,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,aAAa,EAAkB,CAAA;QAE5C,wBAAwB;QACxB,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,EAAE,CAAC;YAC/B,eAAe,CAAC,IAAI,CAAC,CAAA;YACrB,OAAM;QACP,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,IAAI,EAAE,CAAA;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,IAAI,EAAE,CAAA;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,IAAI,CAAC,CAAA;QAEjD,kDAAkD;QAClD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAA;QAEhC,eAAe;QACf,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAE5D,oDAAoD;QACpD,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;QAE5D,wCAAwC;QACxC,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YACtD,eAAe,CAAC,IAAI,CAAC,CAAA;YACrB,OAAM;QACP,CAAC;QAED,yBAAyB;QACzB,MAAM,MAAM,GAAG;YACd,GAAG,IAAI;YACP,WAAW,EAAE;gBACZ,GAAG,IAAI,CAAC,WAAW;gBACnB,MAAM,EAAE,YAAY,CAAC,MAAM;gBAC3B,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;aAC5D;SACD,CAAA;QAED,eAAe,CAAC,MAAM,CAAC,CAAA;IACxB,CAAC;IAAC,OAAM,KAAK,EAAE,CAAC;QACf,yCAAyC;QACzC,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAG,KAAe,CAAC,OAAO,CAAC,CAAA;QAC7D,IAAI,CAAC;YACJ,MAAM,IAAI,GAAG,aAAa,EAAkB,CAAA;YAC5C,eAAe,CAAC,IAAI,CAAC,CAAA;QACtB,CAAC;QAAC,MAAM,CAAC;YACR,oBAAoB;QACrB,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * sherpa pre - PreToolUse hook that blocks dangerous bash commands
3
+ */
4
+ /**
5
+ * Run pre-guard check on a command
6
+ */
7
+ export declare function checkBashCommand(command: string): {
8
+ blocked: boolean;
9
+ rule?: {
10
+ name: string;
11
+ reason: string;
12
+ };
13
+ };
14
+ /**
15
+ * Main entry point for PreToolUse hook
16
+ */
17
+ export declare function runPre(): void;
18
+ //# sourceMappingURL=pre.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre.d.ts","sourceRoot":"","sources":["../../src/commands/pre.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsDH;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAgC/G;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,IAAI,CAyB7B"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * sherpa pre - PreToolUse hook that blocks dangerous bash commands
3
+ */
4
+ import { EXIT, readHookInput } from '@goobits/sherpa-core';
5
+ // @ts-expect-error - bash-parser has no types
6
+ import parse from 'bash-parser';
7
+ import { readFileSync } from 'fs';
8
+ import { dirname, join } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { extractCommands } from '../parser.js';
11
+ import { checkCommand, checkPipeline } from '../rules.js';
12
+ // Load rules from JSON config
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const rulesPath = join(__dirname, '..', '..', 'rules.json');
15
+ const rules = JSON.parse(readFileSync(rulesPath, 'utf-8'));
16
+ // Fast-path: commands that are always safe (skip expensive parse)
17
+ const SAFE_COMMAND_PREFIXES = [
18
+ 'echo ', 'echo\t', 'printf ',
19
+ 'ls ', 'ls\t', 'ls\n', 'ls',
20
+ 'pwd', 'date', 'whoami', 'id',
21
+ 'cat ', 'head ', 'tail ', 'wc ',
22
+ 'grep ', 'awk ', 'sed ',
23
+ 'cd ', 'cd\t',
24
+ 'true', 'false', ':'
25
+ ];
26
+ function isFastPathSafe(command) {
27
+ const trimmed = command.trim();
28
+ // Check if command starts with a known-safe prefix
29
+ for (const prefix of SAFE_COMMAND_PREFIXES) {
30
+ if (trimmed === prefix.trim() || trimmed.startsWith(prefix)) {
31
+ // Quick check: no compound commands, pipes, or dangerous chars
32
+ if (!trimmed.includes('|') &&
33
+ !trimmed.includes('&&') &&
34
+ !trimmed.includes('||') &&
35
+ !trimmed.includes(';') &&
36
+ !trimmed.includes('$(') &&
37
+ !trimmed.includes('`')) {
38
+ return true;
39
+ }
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ /**
45
+ * Run pre-guard check on a command
46
+ */
47
+ export function checkBashCommand(command) {
48
+ // Fast path: skip parsing for known-safe commands
49
+ if (isFastPathSafe(command)) {
50
+ return { blocked: false };
51
+ }
52
+ // Parse command into AST
53
+ let ast;
54
+ try {
55
+ ast = parse(command);
56
+ }
57
+ catch {
58
+ // If parsing fails, allow (fail open)
59
+ return { blocked: false };
60
+ }
61
+ // Check pipeline patterns first (curl | bash)
62
+ const pipeRule = checkPipeline(ast, rules);
63
+ if (pipeRule) {
64
+ return { blocked: true, rule: { name: pipeRule.name, reason: pipeRule.reason } };
65
+ }
66
+ // Extract and check each command
67
+ const commands = extractCommands(ast);
68
+ for (const cmdInfo of commands) {
69
+ const result = checkCommand(cmdInfo, rules);
70
+ if (result.blocked && result.rule) {
71
+ return { blocked: true, rule: { name: result.rule.name, reason: result.rule.reason } };
72
+ }
73
+ }
74
+ return { blocked: false };
75
+ }
76
+ /**
77
+ * Main entry point for PreToolUse hook
78
+ */
79
+ export function runPre() {
80
+ try {
81
+ const data = readHookInput();
82
+ const command = data.tool_input?.command;
83
+ if (!command) {
84
+ process.exit(EXIT.ALLOW);
85
+ }
86
+ const result = checkBashCommand(command);
87
+ if (result.blocked && result.rule) {
88
+ console.error('BLOCKED by sherpa');
89
+ console.error(` Rule: ${result.rule.name}`);
90
+ console.error(` Reason: ${result.rule.reason}`);
91
+ console.error(` Command: ${command}`);
92
+ process.exit(EXIT.BLOCK);
93
+ }
94
+ process.exit(EXIT.ALLOW);
95
+ }
96
+ catch (error) {
97
+ // Graceful degradation: allow on error
98
+ console.error('sherpa pre error:', error.message);
99
+ process.exit(EXIT.ALLOW);
100
+ }
101
+ }
102
+ //# sourceMappingURL=pre.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre.js","sourceRoot":"","sources":["../../src/commands/pre.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * sherpa status - Show LLM provider status and rate limits
3
+ */
4
+ export declare function runStatus(): Promise<void>;
5
+ //# sourceMappingURL=status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAmD/C"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * sherpa status - Show LLM provider status and rate limits
3
+ */
4
+ import { checkProviderStatus, getAvailableProviders } from '@goobits/sherpa-core';
5
+ export async function runStatus() {
6
+ console.log('Checking LLM providers...\n');
7
+ const available = getAvailableProviders();
8
+ if (available.length === 0) {
9
+ console.log('No providers configured.');
10
+ console.log('Set CEREBRAS_API_KEY, GROQ_API_KEY, or OPENAI_API_KEY');
11
+ return;
12
+ }
13
+ console.log(`Configured: ${available.join(', ')}\n`);
14
+ const statuses = await checkProviderStatus();
15
+ for (const status of statuses) {
16
+ const icon = status.available ? '✓' : '✗';
17
+ console.log(`${icon} ${status.provider.toUpperCase()}`);
18
+ if (status.error) {
19
+ console.log(` Error: ${status.error}`);
20
+ continue;
21
+ }
22
+ if (status.limits) {
23
+ const l = status.limits;
24
+ // Show requests
25
+ if (l.requestsPerMinute && l.requestsRemainingMinute) {
26
+ const pct = Math.round((l.requestsRemainingMinute / l.requestsPerMinute) * 100);
27
+ console.log(` Requests/min: ${l.requestsRemainingMinute.toLocaleString()}/${l.requestsPerMinute.toLocaleString()} (${pct}%)`);
28
+ }
29
+ if (l.requestsRemainingDay) {
30
+ const limit = l.requestsPerDay || 14400; // Cerebras free tier default
31
+ const used = limit - l.requestsRemainingDay;
32
+ console.log(` Requests/day: ${l.requestsRemainingDay.toLocaleString()} remaining (${used.toLocaleString()} used)`);
33
+ }
34
+ // Show tokens
35
+ if (l.tokensPerMinute && l.tokensRemainingMinute) {
36
+ const pct = Math.round((l.tokensRemainingMinute / l.tokensPerMinute) * 100);
37
+ console.log(` Tokens/min: ${l.tokensRemainingMinute.toLocaleString()}/${l.tokensPerMinute.toLocaleString()} (${pct}%)`);
38
+ }
39
+ if (l.tokensRemainingDay) {
40
+ const limit = l.tokensPerDay || 1000000; // Cerebras free tier default
41
+ const used = limit - l.tokensRemainingDay;
42
+ console.log(` Tokens/day: ${l.tokensRemainingDay.toLocaleString()} remaining (${used.toLocaleString()} used)`);
43
+ }
44
+ }
45
+ console.log('');
46
+ }
47
+ }
48
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CONFIG,
4
+ checkBashCommand,
5
+ offloadOutput
6
+ } from "./chunk-5NF3BSD6.js";
7
+ import {
8
+ loadConfig
9
+ } from "./chunk-3CILH2TO.js";
10
+
11
+ // src/daemon.ts
12
+ import { existsSync, unlinkSync } from "fs";
13
+ import { createServer } from "net";
14
+ var config = loadConfig("config.json", DEFAULT_CONFIG);
15
+ var socketPath = config.socketPath;
16
+ function handleConnection(socket) {
17
+ let buffer = "";
18
+ socket.on("data", (chunk) => {
19
+ buffer += chunk.toString();
20
+ if (buffer.length > 10 * 1024 * 1024) {
21
+ socket.write(JSON.stringify({ error: "Request too large" }));
22
+ socket.end();
23
+ return;
24
+ }
25
+ try {
26
+ const request = JSON.parse(buffer);
27
+ buffer = "";
28
+ let response;
29
+ if (request.type === "pre") {
30
+ const { command } = request.data;
31
+ const result = checkBashCommand(command);
32
+ response = result;
33
+ } else if (request.type === "post") {
34
+ const { stdout, stderr, exit_code } = request.data;
35
+ const stdoutResult = offloadOutput(stdout, exit_code, config);
36
+ const stderrResult = offloadOutput(stderr, exit_code, config);
37
+ response = {
38
+ stdout: stdoutResult.result,
39
+ stderr: stderrResult.result,
40
+ modified: stdoutResult.modified || stderrResult.modified
41
+ };
42
+ } else {
43
+ response = { error: "Unknown request type" };
44
+ }
45
+ socket.write(JSON.stringify(response));
46
+ socket.end();
47
+ } catch (err) {
48
+ if (err instanceof SyntaxError && !err.message.includes("end of JSON")) {
49
+ buffer = "";
50
+ socket.write(JSON.stringify({ error: "Invalid JSON" }));
51
+ socket.end();
52
+ }
53
+ }
54
+ });
55
+ socket.on("error", (err) => {
56
+ console.error("Socket error:", err.message);
57
+ });
58
+ }
59
+ function cleanup() {
60
+ try {
61
+ if (existsSync(socketPath)) {
62
+ unlinkSync(socketPath);
63
+ }
64
+ } catch {
65
+ }
66
+ process.exit(0);
67
+ }
68
+ function main() {
69
+ if (existsSync(socketPath)) {
70
+ unlinkSync(socketPath);
71
+ }
72
+ const server = createServer(handleConnection);
73
+ process.on("SIGTERM", cleanup);
74
+ process.on("SIGINT", cleanup);
75
+ process.on("SIGHUP", cleanup);
76
+ process.on("uncaughtException", (err) => {
77
+ console.error("Uncaught exception:", err);
78
+ cleanup();
79
+ });
80
+ server.listen(socketPath, () => {
81
+ console.error(`Guard daemon listening on ${socketPath}`);
82
+ });
83
+ server.on("error", (err) => {
84
+ console.error("Server error:", err);
85
+ cleanup();
86
+ });
87
+ }
88
+ main();
89
+ //# sourceMappingURL=daemon-V2QDZTUB.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/daemon.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\n/**\n * Guard daemon - persistent server for fast hook responses\n *\n * Keeps bash-parser and rules loaded in memory.\n * Communicates via Unix socket for sub-5ms response times.\n */\n\nimport { loadConfig } from '@goobits/sherpa-core'\nimport { existsSync, unlinkSync } from 'fs'\nimport { createServer, type Socket } from 'net'\n\nimport { offloadOutput } from './commands/post.js'\nimport { checkBashCommand } from './commands/pre.js'\nimport { DEFAULT_CONFIG, type GuardConfig } from './types.js'\n\n// Load config\nconst config = loadConfig<GuardConfig>('config.json', DEFAULT_CONFIG)\nconst socketPath = config.socketPath\n\ninterface DaemonRequest {\n\ttype: 'pre' | 'post';\n\tdata: unknown;\n}\n\ninterface PreRequest {\n\tcommand: string;\n}\n\ninterface PostRequest {\n\tstdout: string;\n\tstderr: string;\n\texit_code: number;\n}\n\n/**\n * Handle a client connection\n */\nfunction handleConnection(socket: Socket): void {\n\tlet buffer = ''\n\n\tsocket.on('data', chunk => {\n\t\tbuffer += chunk.toString()\n\n\t\t// Limit buffer size to prevent DoS\n\t\tif (buffer.length > 10 * 1024 * 1024) {\n\t\t\tsocket.write(JSON.stringify({ error: 'Request too large' }))\n\t\t\tsocket.end()\n\t\t\treturn\n\t\t}\n\n\t\t// Try to parse complete JSON\n\t\ttry {\n\t\t\tconst request: DaemonRequest = JSON.parse(buffer)\n\t\t\tbuffer = ''\n\n\t\t\tlet response: unknown\n\n\t\t\tif (request.type === 'pre') {\n\t\t\t\tconst { command } = request.data as PreRequest\n\t\t\t\tconst result = checkBashCommand(command)\n\t\t\t\tresponse = result\n\t\t\t} else if (request.type === 'post') {\n\t\t\t\tconst { stdout, stderr, exit_code } = request.data as PostRequest\n\t\t\t\tconst stdoutResult = offloadOutput(stdout, exit_code, config)\n\t\t\t\tconst stderrResult = offloadOutput(stderr, exit_code, config)\n\t\t\t\tresponse = {\n\t\t\t\t\tstdout: stdoutResult.result,\n\t\t\t\t\tstderr: stderrResult.result,\n\t\t\t\t\tmodified: stdoutResult.modified || stderrResult.modified\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresponse = { error: 'Unknown request type' }\n\t\t\t}\n\n\t\t\tsocket.write(JSON.stringify(response))\n\t\t\tsocket.end()\n\t\t} catch(err) {\n\t\t\t// Check if it's a syntax error (invalid JSON) vs incomplete JSON\n\t\t\tif (err instanceof SyntaxError && !err.message.includes('end of JSON')) {\n\t\t\t\t// Invalid JSON structure - reset buffer and report error\n\t\t\t\tbuffer = ''\n\t\t\t\tsocket.write(JSON.stringify({ error: 'Invalid JSON' }))\n\t\t\t\tsocket.end()\n\t\t\t}\n\t\t\t// Otherwise incomplete JSON, wait for more data\n\t\t}\n\t})\n\n\tsocket.on('error', err => {\n\t\tconsole.error('Socket error:', err.message)\n\t})\n}\n\n/**\n * Cleanup socket file on exit\n */\nfunction cleanup(): void {\n\ttry {\n\t\tif (existsSync(socketPath)) {\n\t\t\tunlinkSync(socketPath)\n\t\t}\n\t} catch {\n\t\t// Ignore cleanup errors\n\t}\n\tprocess.exit(0)\n}\n\n/**\n * Start the daemon\n */\nfunction main(): void {\n\t// Remove stale socket file\n\tif (existsSync(socketPath)) {\n\t\tunlinkSync(socketPath)\n\t}\n\n\tconst server = createServer(handleConnection)\n\n\t// Handle shutdown signals\n\tprocess.on('SIGTERM', cleanup)\n\tprocess.on('SIGINT', cleanup)\n\tprocess.on('SIGHUP', cleanup)\n\n\t// Handle uncaught errors\n\tprocess.on('uncaughtException', err => {\n\t\tconsole.error('Uncaught exception:', err)\n\t\tcleanup()\n\t})\n\n\tserver.listen(socketPath, () => {\n\t\tconsole.error(`Guard daemon listening on ${ socketPath }`)\n\t})\n\n\tserver.on('error', err => {\n\t\tconsole.error('Server error:', err)\n\t\tcleanup()\n\t})\n}\n\nmain()\n"],
5
+ "mappings": ";;;;;;;;;;;AASA,SAAS,YAAY,kBAAkB;AACvC,SAAS,oBAAiC;AAO1C,IAAM,SAAS,WAAwB,eAAe,cAAc;AACpE,IAAM,aAAa,OAAO;AAoB1B,SAAS,iBAAiB,QAAsB;AAC/C,MAAI,SAAS;AAEb,SAAO,GAAG,QAAQ,WAAS;AAC1B,cAAU,MAAM,SAAS;AAGzB,QAAI,OAAO,SAAS,KAAK,OAAO,MAAM;AACrC,aAAO,MAAM,KAAK,UAAU,EAAE,OAAO,oBAAoB,CAAC,CAAC;AAC3D,aAAO,IAAI;AACX;AAAA,IACD;AAGA,QAAI;AACH,YAAM,UAAyB,KAAK,MAAM,MAAM;AAChD,eAAS;AAET,UAAI;AAEJ,UAAI,QAAQ,SAAS,OAAO;AAC3B,cAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,cAAM,SAAS,iBAAiB,OAAO;AACvC,mBAAW;AAAA,MACZ,WAAW,QAAQ,SAAS,QAAQ;AACnC,cAAM,EAAE,QAAQ,QAAQ,UAAU,IAAI,QAAQ;AAC9C,cAAM,eAAe,cAAc,QAAQ,WAAW,MAAM;AAC5D,cAAM,eAAe,cAAc,QAAQ,WAAW,MAAM;AAC5D,mBAAW;AAAA,UACV,QAAQ,aAAa;AAAA,UACrB,QAAQ,aAAa;AAAA,UACrB,UAAU,aAAa,YAAY,aAAa;AAAA,QACjD;AAAA,MACD,OAAO;AACN,mBAAW,EAAE,OAAO,uBAAuB;AAAA,MAC5C;AAEA,aAAO,MAAM,KAAK,UAAU,QAAQ,CAAC;AACrC,aAAO,IAAI;AAAA,IACZ,SAAQ,KAAK;AAEZ,UAAI,eAAe,eAAe,CAAC,IAAI,QAAQ,SAAS,aAAa,GAAG;AAEvE,iBAAS;AACT,eAAO,MAAM,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AACtD,eAAO,IAAI;AAAA,MACZ;AAAA,IAED;AAAA,EACD,CAAC;AAED,SAAO,GAAG,SAAS,SAAO;AACzB,YAAQ,MAAM,iBAAiB,IAAI,OAAO;AAAA,EAC3C,CAAC;AACF;AAKA,SAAS,UAAgB;AACxB,MAAI;AACH,QAAI,WAAW,UAAU,GAAG;AAC3B,iBAAW,UAAU;AAAA,IACtB;AAAA,EACD,QAAQ;AAAA,EAER;AACA,UAAQ,KAAK,CAAC;AACf;AAKA,SAAS,OAAa;AAErB,MAAI,WAAW,UAAU,GAAG;AAC3B,eAAW,UAAU;AAAA,EACtB;AAEA,QAAM,SAAS,aAAa,gBAAgB;AAG5C,UAAQ,GAAG,WAAW,OAAO;AAC7B,UAAQ,GAAG,UAAU,OAAO;AAC5B,UAAQ,GAAG,UAAU,OAAO;AAG5B,UAAQ,GAAG,qBAAqB,SAAO;AACtC,YAAQ,MAAM,uBAAuB,GAAG;AACxC,YAAQ;AAAA,EACT,CAAC;AAED,SAAO,OAAO,YAAY,MAAM;AAC/B,YAAQ,MAAM,6BAA8B,UAAW,EAAE;AAAA,EAC1D,CAAC;AAED,SAAO,GAAG,SAAS,SAAO;AACzB,YAAQ,MAAM,iBAAiB,GAAG;AAClC,YAAQ;AAAA,EACT,CAAC;AACF;AAEA,KAAK;",
6
+ "names": []
7
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Guard daemon - persistent server for fast hook responses
4
+ *
5
+ * Keeps bash-parser and rules loaded in memory.
6
+ * Communicates via Unix socket for sub-5ms response times.
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":";AACA;;;;;GAKG"}
package/dist/daemon.js ADDED
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Guard daemon - persistent server for fast hook responses
4
+ *
5
+ * Keeps bash-parser and rules loaded in memory.
6
+ * Communicates via Unix socket for sub-5ms response times.
7
+ */
8
+ import { loadConfig } from '@goobits/sherpa-core';
9
+ import { existsSync, unlinkSync } from 'fs';
10
+ import { createServer } from 'net';
11
+ import { offloadOutput } from './commands/post.js';
12
+ import { checkBashCommand } from './commands/pre.js';
13
+ import { DEFAULT_CONFIG } from './types.js';
14
+ // Load config
15
+ const config = loadConfig('config.json', DEFAULT_CONFIG);
16
+ const socketPath = config.socketPath;
17
+ /**
18
+ * Handle a client connection
19
+ */
20
+ function handleConnection(socket) {
21
+ let buffer = '';
22
+ socket.on('data', chunk => {
23
+ buffer += chunk.toString();
24
+ // Limit buffer size to prevent DoS
25
+ if (buffer.length > 10 * 1024 * 1024) {
26
+ socket.write(JSON.stringify({ error: 'Request too large' }));
27
+ socket.end();
28
+ return;
29
+ }
30
+ // Try to parse complete JSON
31
+ try {
32
+ const request = JSON.parse(buffer);
33
+ buffer = '';
34
+ let response;
35
+ if (request.type === 'pre') {
36
+ const { command } = request.data;
37
+ const result = checkBashCommand(command);
38
+ response = result;
39
+ }
40
+ else if (request.type === 'post') {
41
+ const { stdout, stderr, exit_code } = request.data;
42
+ const stdoutResult = offloadOutput(stdout, exit_code, config);
43
+ const stderrResult = offloadOutput(stderr, exit_code, config);
44
+ response = {
45
+ stdout: stdoutResult.result,
46
+ stderr: stderrResult.result,
47
+ modified: stdoutResult.modified || stderrResult.modified
48
+ };
49
+ }
50
+ else {
51
+ response = { error: 'Unknown request type' };
52
+ }
53
+ socket.write(JSON.stringify(response));
54
+ socket.end();
55
+ }
56
+ catch (err) {
57
+ // Check if it's a syntax error (invalid JSON) vs incomplete JSON
58
+ if (err instanceof SyntaxError && !err.message.includes('end of JSON')) {
59
+ // Invalid JSON structure - reset buffer and report error
60
+ buffer = '';
61
+ socket.write(JSON.stringify({ error: 'Invalid JSON' }));
62
+ socket.end();
63
+ }
64
+ // Otherwise incomplete JSON, wait for more data
65
+ }
66
+ });
67
+ socket.on('error', err => {
68
+ console.error('Socket error:', err.message);
69
+ });
70
+ }
71
+ /**
72
+ * Cleanup socket file on exit
73
+ */
74
+ function cleanup() {
75
+ try {
76
+ if (existsSync(socketPath)) {
77
+ unlinkSync(socketPath);
78
+ }
79
+ }
80
+ catch {
81
+ // Ignore cleanup errors
82
+ }
83
+ process.exit(0);
84
+ }
85
+ /**
86
+ * Start the daemon
87
+ */
88
+ function main() {
89
+ // Remove stale socket file
90
+ if (existsSync(socketPath)) {
91
+ unlinkSync(socketPath);
92
+ }
93
+ const server = createServer(handleConnection);
94
+ // Handle shutdown signals
95
+ process.on('SIGTERM', cleanup);
96
+ process.on('SIGINT', cleanup);
97
+ process.on('SIGHUP', cleanup);
98
+ // Handle uncaught errors
99
+ process.on('uncaughtException', err => {
100
+ console.error('Uncaught exception:', err);
101
+ cleanup();
102
+ });
103
+ server.listen(socketPath, () => {
104
+ console.error(`Guard daemon listening on ${socketPath}`);
105
+ });
106
+ server.on('error', err => {
107
+ console.error('Server error:', err);
108
+ cleanup();
109
+ });
110
+ }
111
+ main();
112
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @goobits/sherpa - MCP hooks and repo setup for safer AI coding
3
+ *
4
+ * CLI commands:
5
+ * sherpa init - Set up repo (husky, lint-staged, gitleaks, claude hooks)
6
+ * sherpa pre - PreToolUse hook (blocks dangerous commands)
7
+ * sherpa post - PostToolUse hook (offloads large output)
8
+ */
9
+ export { runInit } from './commands/init.js';
10
+ export { offloadOutput, runPost } from './commands/post.js';
11
+ export { checkBashCommand, runPre } from './commands/pre.js';
12
+ export * from './parser.js';
13
+ export * from './rules.js';
14
+ export * from './types.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC5D,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}