veto-leash 0.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.
Files changed (135) hide show
  1. package/IMPLEMENTATION_PLAN.md +2194 -0
  2. package/LICENSE +201 -0
  3. package/README.md +260 -0
  4. package/dist/audit/index.d.ts +38 -0
  5. package/dist/audit/index.d.ts.map +1 -0
  6. package/dist/audit/index.js +132 -0
  7. package/dist/audit/index.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +406 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/cloud/index.d.ts +40 -0
  13. package/dist/cloud/index.d.ts.map +1 -0
  14. package/dist/cloud/index.js +115 -0
  15. package/dist/cloud/index.js.map +1 -0
  16. package/dist/compiler/builtins.d.ts +6 -0
  17. package/dist/compiler/builtins.d.ts.map +1 -0
  18. package/dist/compiler/builtins.js +129 -0
  19. package/dist/compiler/builtins.js.map +1 -0
  20. package/dist/compiler/cache.d.ts +6 -0
  21. package/dist/compiler/cache.d.ts.map +1 -0
  22. package/dist/compiler/cache.js +49 -0
  23. package/dist/compiler/cache.js.map +1 -0
  24. package/dist/compiler/index.d.ts +3 -0
  25. package/dist/compiler/index.d.ts.map +1 -0
  26. package/dist/compiler/index.js +48 -0
  27. package/dist/compiler/index.js.map +1 -0
  28. package/dist/compiler/llm.d.ts +3 -0
  29. package/dist/compiler/llm.d.ts.map +1 -0
  30. package/dist/compiler/llm.js +69 -0
  31. package/dist/compiler/llm.js.map +1 -0
  32. package/dist/compiler/prompt.d.ts +2 -0
  33. package/dist/compiler/prompt.d.ts.map +1 -0
  34. package/dist/compiler/prompt.js +37 -0
  35. package/dist/compiler/prompt.js.map +1 -0
  36. package/dist/config/loader.d.ts +22 -0
  37. package/dist/config/loader.d.ts.map +1 -0
  38. package/dist/config/loader.js +100 -0
  39. package/dist/config/loader.js.map +1 -0
  40. package/dist/config/schema.d.ts +42 -0
  41. package/dist/config/schema.d.ts.map +1 -0
  42. package/dist/config/schema.js +93 -0
  43. package/dist/config/schema.js.map +1 -0
  44. package/dist/matcher.d.ts +22 -0
  45. package/dist/matcher.d.ts.map +1 -0
  46. package/dist/matcher.js +69 -0
  47. package/dist/matcher.js.map +1 -0
  48. package/dist/native/aider.d.ts +10 -0
  49. package/dist/native/aider.d.ts.map +1 -0
  50. package/dist/native/aider.js +120 -0
  51. package/dist/native/aider.js.map +1 -0
  52. package/dist/native/claude-code.d.ts +14 -0
  53. package/dist/native/claude-code.d.ts.map +1 -0
  54. package/dist/native/claude-code.js +273 -0
  55. package/dist/native/claude-code.js.map +1 -0
  56. package/dist/native/cursor.d.ts +11 -0
  57. package/dist/native/cursor.d.ts.map +1 -0
  58. package/dist/native/cursor.js +105 -0
  59. package/dist/native/cursor.js.map +1 -0
  60. package/dist/native/index.d.ts +35 -0
  61. package/dist/native/index.d.ts.map +1 -0
  62. package/dist/native/index.js +171 -0
  63. package/dist/native/index.js.map +1 -0
  64. package/dist/native/opencode.d.ts +22 -0
  65. package/dist/native/opencode.d.ts.map +1 -0
  66. package/dist/native/opencode.js +225 -0
  67. package/dist/native/opencode.js.map +1 -0
  68. package/dist/native/windsurf.d.ts +14 -0
  69. package/dist/native/windsurf.d.ts.map +1 -0
  70. package/dist/native/windsurf.js +198 -0
  71. package/dist/native/windsurf.js.map +1 -0
  72. package/dist/types.d.ts +38 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +11 -0
  75. package/dist/types.js.map +1 -0
  76. package/dist/ui/colors.d.ts +21 -0
  77. package/dist/ui/colors.d.ts.map +1 -0
  78. package/dist/ui/colors.js +41 -0
  79. package/dist/ui/colors.js.map +1 -0
  80. package/dist/watchdog/index.d.ts +25 -0
  81. package/dist/watchdog/index.d.ts.map +1 -0
  82. package/dist/watchdog/index.js +57 -0
  83. package/dist/watchdog/index.js.map +1 -0
  84. package/dist/watchdog/restore.d.ts +16 -0
  85. package/dist/watchdog/restore.d.ts.map +1 -0
  86. package/dist/watchdog/restore.js +56 -0
  87. package/dist/watchdog/restore.js.map +1 -0
  88. package/dist/watchdog/snapshot.d.ts +38 -0
  89. package/dist/watchdog/snapshot.d.ts.map +1 -0
  90. package/dist/watchdog/snapshot.js +166 -0
  91. package/dist/watchdog/snapshot.js.map +1 -0
  92. package/dist/watchdog/watcher.d.ts +28 -0
  93. package/dist/watchdog/watcher.d.ts.map +1 -0
  94. package/dist/watchdog/watcher.js +117 -0
  95. package/dist/watchdog/watcher.js.map +1 -0
  96. package/dist/wrapper/daemon.d.ts +12 -0
  97. package/dist/wrapper/daemon.d.ts.map +1 -0
  98. package/dist/wrapper/daemon.js +103 -0
  99. package/dist/wrapper/daemon.js.map +1 -0
  100. package/dist/wrapper/shims.d.ts +4 -0
  101. package/dist/wrapper/shims.d.ts.map +1 -0
  102. package/dist/wrapper/shims.js +390 -0
  103. package/dist/wrapper/shims.js.map +1 -0
  104. package/dist/wrapper/spawn.d.ts +4 -0
  105. package/dist/wrapper/spawn.d.ts.map +1 -0
  106. package/dist/wrapper/spawn.js +35 -0
  107. package/dist/wrapper/spawn.js.map +1 -0
  108. package/package.json +46 -0
  109. package/src/audit/index.ts +172 -0
  110. package/src/cli.ts +503 -0
  111. package/src/cloud/index.ts +139 -0
  112. package/src/compiler/builtins.ts +137 -0
  113. package/src/compiler/cache.ts +51 -0
  114. package/src/compiler/index.ts +59 -0
  115. package/src/compiler/llm.ts +83 -0
  116. package/src/compiler/prompt.ts +37 -0
  117. package/src/config/loader.ts +126 -0
  118. package/src/config/schema.ts +136 -0
  119. package/src/matcher.ts +89 -0
  120. package/src/native/aider.ts +150 -0
  121. package/src/native/claude-code.ts +308 -0
  122. package/src/native/cursor.ts +131 -0
  123. package/src/native/index.ts +233 -0
  124. package/src/native/opencode.ts +310 -0
  125. package/src/native/windsurf.ts +231 -0
  126. package/src/types.ts +48 -0
  127. package/src/ui/colors.ts +50 -0
  128. package/src/watchdog/index.ts +82 -0
  129. package/src/watchdog/restore.ts +74 -0
  130. package/src/watchdog/snapshot.ts +209 -0
  131. package/src/watchdog/watcher.ts +150 -0
  132. package/src/wrapper/daemon.ts +133 -0
  133. package/src/wrapper/shims.ts +409 -0
  134. package/src/wrapper/spawn.ts +47 -0
  135. package/tsconfig.json +20 -0
@@ -0,0 +1,93 @@
1
+ // src/config/schema.ts
2
+ // .leash file schema and validation
3
+ /**
4
+ * Default settings
5
+ */
6
+ export const DEFAULT_SETTINGS = {
7
+ fail_closed: true,
8
+ audit_log: false,
9
+ verbose: false,
10
+ };
11
+ /**
12
+ * Validate a .leash config object
13
+ */
14
+ export function validateConfig(config) {
15
+ if (typeof config !== 'object' || config === null) {
16
+ return false;
17
+ }
18
+ const c = config;
19
+ // Version check
20
+ if (c.version !== 1) {
21
+ return false;
22
+ }
23
+ // Policies must be array of strings
24
+ if (!Array.isArray(c.policies)) {
25
+ return false;
26
+ }
27
+ for (const policy of c.policies) {
28
+ if (typeof policy !== 'string') {
29
+ return false;
30
+ }
31
+ }
32
+ // Settings are optional
33
+ if (c.settings !== undefined) {
34
+ if (typeof c.settings !== 'object' || c.settings === null) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
40
+ /**
41
+ * Generate a default .leash config
42
+ */
43
+ export function generateDefaultConfig() {
44
+ return {
45
+ version: 1,
46
+ policies: [
47
+ "don't delete test files",
48
+ "protect .env",
49
+ ],
50
+ settings: {
51
+ fail_closed: true,
52
+ audit_log: false,
53
+ },
54
+ };
55
+ }
56
+ /**
57
+ * Generate YAML content for a .leash file
58
+ */
59
+ export function generateLeashYaml(config) {
60
+ const lines = [
61
+ '# .leash - Veto Leash project configuration',
62
+ '# Commit this file to version control',
63
+ '',
64
+ 'version: 1',
65
+ '',
66
+ '# Natural language restrictions',
67
+ 'policies:',
68
+ ];
69
+ for (const policy of config.policies) {
70
+ lines.push(` - "${policy}"`);
71
+ }
72
+ if (config.settings) {
73
+ lines.push('');
74
+ lines.push('# Optional settings');
75
+ lines.push('settings:');
76
+ if (config.settings.fail_closed !== undefined) {
77
+ lines.push(` fail_closed: ${config.settings.fail_closed}`);
78
+ }
79
+ if (config.settings.audit_log !== undefined) {
80
+ lines.push(` audit_log: ${config.settings.audit_log}`);
81
+ }
82
+ }
83
+ if (config.cloud) {
84
+ lines.push('');
85
+ lines.push('# Leash Cloud (coming soon)');
86
+ lines.push('# cloud:');
87
+ lines.push('# team_id: "team_xxx"');
88
+ lines.push('# sync: true');
89
+ }
90
+ lines.push('');
91
+ return lines.join('\n');
92
+ }
93
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { Policy } from './types.js';
2
+ /**
3
+ * Normalize a path for cross-platform pattern matching.
4
+ * - Converts backslashes to forward slashes (Windows paths)
5
+ * - Removes trailing slashes
6
+ * - Normalizes . and .. segments
7
+ */
8
+ export declare function normalizePath(p: string): string;
9
+ /**
10
+ * Check if a target path is protected by the policy.
11
+ * Returns true if the target matches include patterns and doesn't match exclude patterns.
12
+ */
13
+ export declare function isProtected(target: string, policy: Policy): boolean;
14
+ /**
15
+ * Get all files in a list that would be protected by the policy.
16
+ */
17
+ export declare function getProtectedFiles(files: string[], policy: Policy): string[];
18
+ /**
19
+ * Get all files in a list that would be excluded (allowed) by the policy.
20
+ */
21
+ export declare function getExcludedFiles(files: string[], policy: Policy): string[];
22
+ //# sourceMappingURL=matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAUzC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CA2B/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAenE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAE3E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1E"}
@@ -0,0 +1,69 @@
1
+ // src/matcher.ts
2
+ import micromatch from 'micromatch';
3
+ const { isMatch } = micromatch;
4
+ const MATCH_OPTIONS = {
5
+ basename: true, // *.test.ts matches src/foo.test.ts
6
+ dot: true, // Match dotfiles
7
+ nocase: true, // Case insensitive (important for Windows)
8
+ };
9
+ /**
10
+ * Normalize a path for cross-platform pattern matching.
11
+ * - Converts backslashes to forward slashes (Windows paths)
12
+ * - Removes trailing slashes
13
+ * - Normalizes . and .. segments
14
+ */
15
+ export function normalizePath(p) {
16
+ // Convert Windows backslashes to forward slashes
17
+ let normalized = p.replace(/\\/g, '/');
18
+ // Remove trailing slash
19
+ if (normalized.endsWith('/') && normalized.length > 1) {
20
+ normalized = normalized.slice(0, -1);
21
+ }
22
+ // Simple normalization of . and ..
23
+ const parts = normalized.split('/');
24
+ const result = [];
25
+ for (const part of parts) {
26
+ if (part === '..') {
27
+ result.pop();
28
+ }
29
+ else if (part !== '.' && part !== '') {
30
+ result.push(part);
31
+ }
32
+ }
33
+ // Preserve leading slash for absolute paths
34
+ if (normalized.startsWith('/')) {
35
+ return '/' + result.join('/');
36
+ }
37
+ return result.join('/') || '.';
38
+ }
39
+ /**
40
+ * Check if a target path is protected by the policy.
41
+ * Returns true if the target matches include patterns and doesn't match exclude patterns.
42
+ */
43
+ export function isProtected(target, policy) {
44
+ // Normalize the target path for cross-platform matching
45
+ const normalizedTarget = normalizePath(target);
46
+ const matchesInclude = policy.include.some((p) => isMatch(normalizedTarget, p, MATCH_OPTIONS));
47
+ if (!matchesInclude)
48
+ return false;
49
+ const matchesExclude = policy.exclude.some((p) => isMatch(normalizedTarget, p, MATCH_OPTIONS));
50
+ return !matchesExclude;
51
+ }
52
+ /**
53
+ * Get all files in a list that would be protected by the policy.
54
+ */
55
+ export function getProtectedFiles(files, policy) {
56
+ return files.filter((f) => isProtected(f, policy));
57
+ }
58
+ /**
59
+ * Get all files in a list that would be excluded (allowed) by the policy.
60
+ */
61
+ export function getExcludedFiles(files, policy) {
62
+ return files.filter((f) => {
63
+ const matchesInclude = policy.include.some((p) => isMatch(f, p, MATCH_OPTIONS));
64
+ if (!matchesInclude)
65
+ return false;
66
+ return policy.exclude.some((p) => isMatch(f, p, MATCH_OPTIONS));
67
+ });
68
+ }
69
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA,iBAAiB;AAEjB,OAAO,UAAU,MAAM,YAAY,CAAC;AAGpC,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;AAE/B,MAAM,aAAa,GAAG;IACpB,QAAQ,EAAE,IAAI,EAAE,oCAAoC;IACpD,GAAG,EAAE,IAAI,EAAE,iBAAiB;IAC5B,MAAM,EAAE,IAAI,EAAE,2CAA2C;CAC1D,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS;IACrC,iDAAiD;IACjD,IAAI,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAEvC,wBAAwB;IACxB,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtD,UAAU,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,mCAAmC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,EAAE,CAAC;QACf,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,IAAI,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,MAAc;IACxD,wDAAwD;IACxD,MAAM,gBAAgB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAE/C,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC,EAAE,aAAa,CAAC,CAC5C,CAAC;IAEF,IAAI,CAAC,cAAc;QAAE,OAAO,KAAK,CAAC;IAElC,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/C,OAAO,CAAC,gBAAgB,EAAE,CAAC,EAAE,aAAa,CAAC,CAC5C,CAAC;IAEF,OAAO,CAAC,cAAc,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAe,EAAE,MAAc;IAC/D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAe,EAAE,MAAc;IAC9D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACxB,MAAM,cAAc,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/C,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAC7B,CAAC;QACF,IAAI,CAAC,cAAc;YAAE,OAAO,KAAK,CAAC;QAElC,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Install veto-leash restrictions into Aider config
3
+ * Uses the 'read' option to mark files as read-only
4
+ */
5
+ export declare function installAiderConfig(target?: 'project' | 'global'): Promise<void>;
6
+ /**
7
+ * Uninstall veto-leash from Aider config
8
+ */
9
+ export declare function uninstallAiderConfig(target?: 'project' | 'global'): Promise<void>;
10
+ //# sourceMappingURL=aider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aider.d.ts","sourceRoot":"","sources":["../../src/native/aider.ts"],"names":[],"mappings":"AAcA;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,GAAE,SAAS,GAAG,QAAoB,GACvC,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAmDD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,GAAE,SAAS,GAAG,QAAoB,GACvC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -0,0 +1,120 @@
1
+ // src/native/aider.ts
2
+ // Aider integration via .aider.conf.yml
3
+ // Aider supports read-only files via the 'read' config option
4
+ import { existsSync, writeFileSync, readFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { COLORS, SYMBOLS } from '../ui/colors.js';
8
+ import { glob } from 'glob';
9
+ const AIDER_CONFIG_FILE = '.aider.conf.yml';
10
+ const AIDER_GLOBAL_CONFIG = join(homedir(), '.aider.conf.yml');
11
+ /**
12
+ * Install veto-leash restrictions into Aider config
13
+ * Uses the 'read' option to mark files as read-only
14
+ */
15
+ export async function installAiderConfig(target = 'project') {
16
+ console.log(`\n${COLORS.info}Installing veto-leash for Aider (${target})...${COLORS.reset}\n`);
17
+ const policies = loadStoredPolicies();
18
+ if (policies.length === 0) {
19
+ console.log(`${COLORS.warning}${SYMBOLS.warning} No policies found. Add policies first:${COLORS.reset}`);
20
+ console.log(` ${COLORS.dim}leash add "protect .env"${COLORS.reset}\n`);
21
+ return;
22
+ }
23
+ const configPath = target === 'global' ? AIDER_GLOBAL_CONFIG : AIDER_CONFIG_FILE;
24
+ // Collect protected files based on policies
25
+ const protectedFiles = await collectProtectedFiles(policies);
26
+ if (protectedFiles.length === 0) {
27
+ console.log(`${COLORS.warning}${SYMBOLS.warning} No matching files found in current directory${COLORS.reset}`);
28
+ return;
29
+ }
30
+ // Generate YAML config
31
+ const yamlContent = generateAiderYaml(protectedFiles, configPath);
32
+ writeFileSync(configPath, yamlContent);
33
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Updated: ${configPath}`);
34
+ console.log(` ${COLORS.dim}Protected ${protectedFiles.length} files as read-only${COLORS.reset}`);
35
+ console.log(`\n${COLORS.warning}${SYMBOLS.warning} Note: Aider 'read' makes files read-only, preventing modifications.${COLORS.reset}`);
36
+ console.log(`For delete protection, use wrapper mode:`);
37
+ console.log(` ${COLORS.dim}leash aider "<restriction>"${COLORS.reset}\n`);
38
+ }
39
+ async function collectProtectedFiles(policies) {
40
+ const files = [];
41
+ for (const policy of policies) {
42
+ // Only protect modify actions (read-only)
43
+ if (policy.action !== 'modify' && policy.action !== 'delete')
44
+ continue;
45
+ for (const pattern of policy.include) {
46
+ const matches = await glob(pattern, {
47
+ dot: true,
48
+ nodir: true,
49
+ ignore: ['node_modules/**', '.git/**'],
50
+ });
51
+ files.push(...matches);
52
+ }
53
+ }
54
+ // Dedupe
55
+ return [...new Set(files)];
56
+ }
57
+ function generateAiderYaml(protectedFiles, existingPath) {
58
+ let existing = '';
59
+ let existingLines = [];
60
+ if (existsSync(existingPath)) {
61
+ existing = readFileSync(existingPath, 'utf-8');
62
+ existingLines = existing.split('\n');
63
+ // Remove existing veto-leash section
64
+ const startIdx = existingLines.findIndex(l => l.includes('# veto-leash'));
65
+ const endIdx = existingLines.findIndex((l, i) => i > startIdx && l.includes('# end veto-leash'));
66
+ if (startIdx !== -1 && endIdx !== -1) {
67
+ existingLines.splice(startIdx, endIdx - startIdx + 1);
68
+ }
69
+ }
70
+ // Add veto-leash section
71
+ const vetoSection = [
72
+ '# veto-leash protected files (read-only)',
73
+ 'read:',
74
+ ...protectedFiles.map(f => ` - ${f}`),
75
+ '# end veto-leash',
76
+ ];
77
+ return [...existingLines.filter(l => l.trim()), '', ...vetoSection].join('\n');
78
+ }
79
+ /**
80
+ * Uninstall veto-leash from Aider config
81
+ */
82
+ export async function uninstallAiderConfig(target = 'project') {
83
+ const configPath = target === 'global' ? AIDER_GLOBAL_CONFIG : AIDER_CONFIG_FILE;
84
+ if (!existsSync(configPath)) {
85
+ console.log(`${COLORS.dim}No Aider config found at ${configPath}${COLORS.reset}`);
86
+ return;
87
+ }
88
+ const content = readFileSync(configPath, 'utf-8');
89
+ const lines = content.split('\n');
90
+ const startIdx = lines.findIndex(l => l.includes('# veto-leash'));
91
+ const endIdx = lines.findIndex((l, i) => i > startIdx && l.includes('# end veto-leash'));
92
+ if (startIdx !== -1 && endIdx !== -1) {
93
+ lines.splice(startIdx, endIdx - startIdx + 1);
94
+ const updated = lines.filter(l => l.trim()).join('\n');
95
+ if (updated.trim()) {
96
+ writeFileSync(configPath, updated);
97
+ }
98
+ else {
99
+ require('fs').unlinkSync(configPath);
100
+ }
101
+ console.log(`${COLORS.success}${SYMBOLS.success} Removed veto-leash from ${configPath}${COLORS.reset}`);
102
+ }
103
+ else {
104
+ console.log(`${COLORS.dim}No veto-leash config found in ${configPath}${COLORS.reset}`);
105
+ }
106
+ }
107
+ function loadStoredPolicies() {
108
+ const policiesFile = join(homedir(), '.config', 'veto-leash', 'policies.json');
109
+ try {
110
+ if (existsSync(policiesFile)) {
111
+ const data = JSON.parse(readFileSync(policiesFile, 'utf-8'));
112
+ return data.policies?.map((p) => p.policy) || [];
113
+ }
114
+ }
115
+ catch {
116
+ // Ignore
117
+ }
118
+ return [];
119
+ }
120
+ //# sourceMappingURL=aider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aider.js","sourceRoot":"","sources":["../../src/native/aider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Policy } from '../types.js';
2
+ /**
3
+ * Install veto-leash as a Claude Code PreToolUse hook
4
+ */
5
+ export declare function installClaudeCodeHook(): Promise<void>;
6
+ /**
7
+ * Add a policy to Claude Code's veto-leash policies
8
+ */
9
+ export declare function addClaudeCodePolicy(policy: Policy, name: string): Promise<void>;
10
+ /**
11
+ * Uninstall veto-leash from Claude Code
12
+ */
13
+ export declare function uninstallClaudeCodeHook(): Promise<void>;
14
+ //# sourceMappingURL=claude-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/native/claude-code.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAoB1C;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CA+D3D;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrF;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CAwB7D"}
@@ -0,0 +1,273 @@
1
+ // src/native/claude-code.ts
2
+ // Claude Code native hook integration
3
+ // Generates PreToolUse hooks that integrate directly with Claude Code's permission system
4
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { COLORS, SYMBOLS } from '../ui/colors.js';
8
+ const CLAUDE_CONFIG_DIR = join(homedir(), '.claude');
9
+ const CLAUDE_HOOKS_DIR = join(CLAUDE_CONFIG_DIR, 'hooks', 'veto-leash');
10
+ const CLAUDE_SETTINGS_FILE = join(CLAUDE_CONFIG_DIR, 'settings.json');
11
+ /**
12
+ * Install veto-leash as a Claude Code PreToolUse hook
13
+ */
14
+ export async function installClaudeCodeHook() {
15
+ console.log(`\n${COLORS.info}Installing veto-leash for Claude Code...${COLORS.reset}\n`);
16
+ // Create hooks directory
17
+ mkdirSync(CLAUDE_HOOKS_DIR, { recursive: true });
18
+ mkdirSync(join(CLAUDE_HOOKS_DIR, 'policies'), { recursive: true });
19
+ // Write the validator script (Python for cross-platform compatibility)
20
+ const validatorPath = join(CLAUDE_HOOKS_DIR, 'validator.py');
21
+ writeFileSync(validatorPath, VALIDATOR_SCRIPT, { mode: 0o755 });
22
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Created validator: ${validatorPath}`);
23
+ // Update Claude settings
24
+ let settings = {};
25
+ if (existsSync(CLAUDE_SETTINGS_FILE)) {
26
+ try {
27
+ settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
28
+ }
29
+ catch {
30
+ // Start fresh if parse fails
31
+ }
32
+ }
33
+ // Ensure hooks structure exists
34
+ if (!settings.hooks) {
35
+ settings.hooks = {};
36
+ }
37
+ if (!settings.hooks.PreToolUse) {
38
+ settings.hooks.PreToolUse = [];
39
+ }
40
+ // Check if veto-leash hook already exists
41
+ const existingIndex = settings.hooks.PreToolUse.findIndex((h) => h.hooks.some((cmd) => cmd.command.includes('veto-leash')));
42
+ const hookEntry = {
43
+ matcher: 'Bash|Write|Edit|MultiEdit',
44
+ hooks: [
45
+ {
46
+ type: 'command',
47
+ command: `python3 "${validatorPath}"`,
48
+ },
49
+ ],
50
+ };
51
+ if (existingIndex >= 0) {
52
+ settings.hooks.PreToolUse[existingIndex] = hookEntry;
53
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Updated hook in settings`);
54
+ }
55
+ else {
56
+ settings.hooks.PreToolUse.push(hookEntry);
57
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Added hook to settings`);
58
+ }
59
+ // Write settings back
60
+ mkdirSync(CLAUDE_CONFIG_DIR, { recursive: true });
61
+ writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
62
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Updated: ${CLAUDE_SETTINGS_FILE}`);
63
+ console.log(`\n${COLORS.success}${SYMBOLS.success} veto-leash installed for Claude Code${COLORS.reset}\n`);
64
+ console.log(`To add a policy:`);
65
+ console.log(` ${COLORS.dim}leash add "don't delete test files"${COLORS.reset}\n`);
66
+ console.log(`To remove:`);
67
+ console.log(` ${COLORS.dim}leash uninstall cc${COLORS.reset}\n`);
68
+ }
69
+ /**
70
+ * Add a policy to Claude Code's veto-leash policies
71
+ */
72
+ export async function addClaudeCodePolicy(policy, name) {
73
+ const policiesDir = join(CLAUDE_HOOKS_DIR, 'policies');
74
+ mkdirSync(policiesDir, { recursive: true });
75
+ const policyFile = join(policiesDir, `${name}.json`);
76
+ writeFileSync(policyFile, JSON.stringify(policy, null, 2));
77
+ console.log(`${COLORS.success}${SYMBOLS.success}${COLORS.reset} Policy saved: ${policyFile}`);
78
+ }
79
+ /**
80
+ * Uninstall veto-leash from Claude Code
81
+ */
82
+ export async function uninstallClaudeCodeHook() {
83
+ console.log(`\n${COLORS.info}Removing veto-leash from Claude Code...${COLORS.reset}\n`);
84
+ // Remove from settings
85
+ if (existsSync(CLAUDE_SETTINGS_FILE)) {
86
+ try {
87
+ const settings = JSON.parse(readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
88
+ if (settings.hooks?.PreToolUse) {
89
+ settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((h) => !h.hooks.some((cmd) => cmd.command.includes('veto-leash')));
90
+ writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
91
+ console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Removed hook from settings`);
92
+ }
93
+ }
94
+ catch {
95
+ console.log(` ${COLORS.warning}${SYMBOLS.warning}${COLORS.reset} Could not parse settings file`);
96
+ }
97
+ }
98
+ console.log(`\n${COLORS.success}${SYMBOLS.success} veto-leash removed from Claude Code${COLORS.reset}\n`);
99
+ console.log(`${COLORS.dim}Note: Policy files in ${CLAUDE_HOOKS_DIR} were preserved.${COLORS.reset}\n`);
100
+ }
101
+ /**
102
+ * Python validator script that runs as a Claude Code PreToolUse hook.
103
+ * Uses Python for maximum cross-platform compatibility.
104
+ */
105
+ const VALIDATOR_SCRIPT = `#!/usr/bin/env python3
106
+ """
107
+ veto-leash validator for Claude Code PreToolUse hooks.
108
+ Checks tool inputs against configured policies.
109
+
110
+ Exit codes:
111
+ 0 = allow
112
+ 2 = block (stderr shown to Claude)
113
+ """
114
+
115
+ import json
116
+ import sys
117
+ import os
118
+ import re
119
+ from pathlib import Path
120
+ from fnmatch import fnmatch
121
+
122
+ POLICIES_DIR = Path(__file__).parent / "policies"
123
+
124
+ def load_policies():
125
+ """Load all policy files from the policies directory."""
126
+ policies = []
127
+ if POLICIES_DIR.exists():
128
+ for f in POLICIES_DIR.glob("*.json"):
129
+ try:
130
+ policies.append(json.loads(f.read_text()))
131
+ except:
132
+ pass
133
+ return policies
134
+
135
+ def normalize_path(p):
136
+ """Normalize path for pattern matching."""
137
+ # Convert to forward slashes for consistent matching
138
+ return str(p).replace("\\\\", "/")
139
+
140
+ def matches_pattern(target, pattern):
141
+ """Check if target matches a glob pattern."""
142
+ target = normalize_path(target)
143
+ pattern = normalize_path(pattern)
144
+
145
+ # Also try matching just the basename
146
+ basename = os.path.basename(target)
147
+
148
+ return fnmatch(target, pattern) or fnmatch(basename, pattern)
149
+
150
+ def is_protected(target, policy):
151
+ """Check if target is protected by policy."""
152
+ # Must match at least one include pattern
153
+ matches_include = any(
154
+ matches_pattern(target, p) for p in policy.get("include", [])
155
+ )
156
+ if not matches_include:
157
+ return False
158
+
159
+ # Must not match any exclude pattern
160
+ matches_exclude = any(
161
+ matches_pattern(target, p) for p in policy.get("exclude", [])
162
+ )
163
+
164
+ return not matches_exclude
165
+
166
+ def parse_bash_targets(command, action):
167
+ """Extract file targets from bash commands."""
168
+ targets = []
169
+
170
+ # rm command patterns
171
+ if action == "delete":
172
+ # Match: rm, rm -rf, rm -f, etc.
173
+ rm_match = re.search(r'\\brm\\s+(?:-[rfiv]+\\s+)*(.+)', command)
174
+ if rm_match:
175
+ args = rm_match.group(1)
176
+ # Split on spaces, filter out flags
177
+ for arg in args.split():
178
+ if not arg.startswith('-'):
179
+ targets.append(arg)
180
+
181
+ # git rm
182
+ git_rm_match = re.search(r'\\bgit\\s+rm\\s+(?:-[rf]+\\s+)*(.+)', command)
183
+ if git_rm_match:
184
+ for arg in git_rm_match.group(1).split():
185
+ if not arg.startswith('-'):
186
+ targets.append(arg)
187
+
188
+ # mv/cp for modify action
189
+ elif action == "modify":
190
+ mv_match = re.search(r'\\b(mv|cp)\\s+(?:-[a-z]+\\s+)*(.+)', command)
191
+ if mv_match:
192
+ args = mv_match.group(2).split()
193
+ # First non-flag arg is source
194
+ for arg in args:
195
+ if not arg.startswith('-'):
196
+ targets.append(arg)
197
+ break
198
+
199
+ return targets
200
+
201
+ def get_action_for_tool(tool_name):
202
+ """Map Claude Code tool names to veto-leash actions."""
203
+ mapping = {
204
+ "Bash": ["delete", "modify", "execute"],
205
+ "Write": ["modify"],
206
+ "Edit": ["modify"],
207
+ "MultiEdit": ["modify"],
208
+ "Read": ["read"],
209
+ }
210
+ return mapping.get(tool_name, [])
211
+
212
+ def main():
213
+ try:
214
+ input_data = json.load(sys.stdin)
215
+ except json.JSONDecodeError:
216
+ sys.exit(0) # Allow if can't parse
217
+
218
+ tool_name = input_data.get("tool_name", "")
219
+ tool_input = input_data.get("tool_input", {})
220
+ cwd = input_data.get("cwd", os.getcwd())
221
+
222
+ policies = load_policies()
223
+ if not policies:
224
+ sys.exit(0) # No policies = allow all
225
+
226
+ # Extract targets based on tool
227
+ targets = []
228
+
229
+ if tool_name == "Bash":
230
+ command = tool_input.get("command", "")
231
+ for policy in policies:
232
+ action = policy.get("action", "modify")
233
+ targets.extend(parse_bash_targets(command, action))
234
+
235
+ elif tool_name in ("Write", "Edit", "MultiEdit"):
236
+ file_path = tool_input.get("file_path", "")
237
+ if file_path:
238
+ targets.append(file_path)
239
+
240
+ if not targets:
241
+ sys.exit(0) # No targets = allow
242
+
243
+ # Check each target against policies
244
+ for target in targets:
245
+ # Make relative to cwd if absolute
246
+ try:
247
+ target_path = Path(target)
248
+ if not target_path.is_absolute():
249
+ target_path = Path(cwd) / target
250
+ rel_target = str(target_path.relative_to(cwd))
251
+ except:
252
+ rel_target = target
253
+
254
+ for policy in policies:
255
+ tool_actions = get_action_for_tool(tool_name)
256
+ policy_action = policy.get("action", "modify")
257
+
258
+ if policy_action in tool_actions:
259
+ if is_protected(rel_target, policy):
260
+ # Block and report to Claude
261
+ desc = policy.get("description", "Protected file")
262
+ print(f"veto-leash: BLOCKED {policy_action}", file=sys.stderr)
263
+ print(f" Target: {rel_target}", file=sys.stderr)
264
+ print(f" Policy: {desc}", file=sys.stderr)
265
+ print(f" Filesystem unchanged.", file=sys.stderr)
266
+ sys.exit(2)
267
+
268
+ sys.exit(0) # Allow
269
+
270
+ if __name__ == "__main__":
271
+ main()
272
+ `;
273
+ //# sourceMappingURL=claude-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/native/claude-code.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,sCAAsC;AACtC,0FAA0F;AAE1F,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAE7B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAElD,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACrD,MAAM,gBAAgB,GAAG,IAAI,CAAC,iBAAiB,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;AACxE,MAAM,oBAAoB,GAAG,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC;AAetE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,2CAA2C,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IAEzF,yBAAyB;IACzB,SAAS,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,SAAS,CAAC,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnE,uEAAuE;IACvE,MAAM,aAAa,GAAG,IAAI,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IAC7D,aAAa,CAAC,aAAa,EAAE,gBAAgB,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,uBAAuB,aAAa,EAAE,CAAC,CAAC;IAExG,yBAAyB;IACzB,IAAI,QAAQ,GAAmB,EAAE,CAAC;IAClC,IAAI,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACpB,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QAC/B,QAAQ,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE,CAAC;IACjC,CAAC;IAED,0CAA0C;IAC1C,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CACvD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CACjE,CAAC;IAEF,MAAM,SAAS,GAAG;QAChB,OAAO,EAAE,2BAA2B;QACpC,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,YAAY,aAAa,GAAG;aACtC;SACF;KACF,CAAC;IAEF,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACvB,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,aAAa,CAAC,GAAG,SAAS,CAAC;QACrD,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,2BAA2B,CAAC,CAAC;IAC/F,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,yBAAyB,CAAC,CAAC;IAC7F,CAAC;IAED,sBAAsB;IACtB,SAAS,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,aAAa,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,aAAa,oBAAoB,EAAE,CAAC,CAAC;IAErG,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,wCAAwC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IAC3G,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,sCAAsC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,GAAG,qBAAqB,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AACpE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,IAAY;IACpE,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IACvD,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE5C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IACrD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,kBAAkB,UAAU,EAAE,CAAC,CAAC;AAChG,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,0CAA0C,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IAExF,uBAAuB;IACvB,IAAI,UAAU,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAmB,IAAI,CAAC,KAAK,CACzC,YAAY,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAC5C,CAAC;YAEF,IAAI,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC;gBAC/B,QAAQ,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAC1D,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAClE,CAAC;gBACF,aAAa,CAAC,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBACvE,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,6BAA6B,CAAC,CAAC;YACjG,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,gCAAgC,CAAC,CAAC;QACpG,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,uCAAuC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IAC1G,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,yBAAyB,gBAAgB,mBAAmB,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AACzG,CAAC;AAED;;;GAGG;AACH,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuKxB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Install veto-leash instructions into .cursorrules
3
+ * Note: This only provides AI guidance, not enforcement.
4
+ * For actual enforcement, use wrapper mode: leash cursor "..."
5
+ */
6
+ export declare function installCursorRules(): Promise<void>;
7
+ /**
8
+ * Uninstall veto-leash from .cursorrules
9
+ */
10
+ export declare function uninstallCursorRules(): Promise<void>;
11
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/native/cursor.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAqCxD;AAkCD;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB1D"}