veto-leash 0.1.0 → 0.1.2

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 (43) hide show
  1. package/dist/cli.js +27 -6
  2. package/dist/cli.js.map +1 -1
  3. package/dist/watchdog/index.d.ts +1 -1
  4. package/dist/watchdog/index.d.ts.map +1 -1
  5. package/dist/watchdog/index.js +6 -1
  6. package/dist/watchdog/index.js.map +1 -1
  7. package/dist/wrapper/daemon.d.ts +3 -1
  8. package/dist/wrapper/daemon.d.ts.map +1 -1
  9. package/dist/wrapper/daemon.js +9 -1
  10. package/dist/wrapper/daemon.js.map +1 -1
  11. package/dist/wrapper/sessions.d.ts +29 -0
  12. package/dist/wrapper/sessions.d.ts.map +1 -0
  13. package/dist/wrapper/sessions.js +101 -0
  14. package/dist/wrapper/sessions.js.map +1 -0
  15. package/package.json +11 -3
  16. package/IMPLEMENTATION_PLAN.md +0 -2194
  17. package/src/audit/index.ts +0 -172
  18. package/src/cli.ts +0 -503
  19. package/src/cloud/index.ts +0 -139
  20. package/src/compiler/builtins.ts +0 -137
  21. package/src/compiler/cache.ts +0 -51
  22. package/src/compiler/index.ts +0 -59
  23. package/src/compiler/llm.ts +0 -83
  24. package/src/compiler/prompt.ts +0 -37
  25. package/src/config/loader.ts +0 -126
  26. package/src/config/schema.ts +0 -136
  27. package/src/matcher.ts +0 -89
  28. package/src/native/aider.ts +0 -150
  29. package/src/native/claude-code.ts +0 -308
  30. package/src/native/cursor.ts +0 -131
  31. package/src/native/index.ts +0 -233
  32. package/src/native/opencode.ts +0 -310
  33. package/src/native/windsurf.ts +0 -231
  34. package/src/types.ts +0 -48
  35. package/src/ui/colors.ts +0 -50
  36. package/src/watchdog/index.ts +0 -82
  37. package/src/watchdog/restore.ts +0 -74
  38. package/src/watchdog/snapshot.ts +0 -209
  39. package/src/watchdog/watcher.ts +0 -150
  40. package/src/wrapper/daemon.ts +0 -133
  41. package/src/wrapper/shims.ts +0 -409
  42. package/src/wrapper/spawn.ts +0 -47
  43. package/tsconfig.json +0 -20
@@ -1,131 +0,0 @@
1
- // src/native/cursor.ts
2
- // Cursor integration via .cursorrules
3
- // Cursor doesn't have a hook/permission system - only AI instruction rules
4
- // We generate .cursorrules that instruct the AI to respect restrictions
5
-
6
- import { existsSync, writeFileSync, readFileSync, appendFileSync } from 'fs';
7
- import { join } from 'path';
8
- import type { Policy } from '../types.js';
9
- import { COLORS, SYMBOLS } from '../ui/colors.js';
10
-
11
- const CURSORRULES_FILE = '.cursorrules';
12
-
13
- /**
14
- * Install veto-leash instructions into .cursorrules
15
- * Note: This only provides AI guidance, not enforcement.
16
- * For actual enforcement, use wrapper mode: leash cursor "..."
17
- */
18
- export async function installCursorRules(): Promise<void> {
19
- console.log(`\n${COLORS.info}Installing veto-leash for Cursor...${COLORS.reset}\n`);
20
-
21
- const policies = loadStoredPolicies();
22
-
23
- if (policies.length === 0) {
24
- console.log(`${COLORS.warning}${SYMBOLS.warning} No policies found. Add policies first:${COLORS.reset}`);
25
- console.log(` ${COLORS.dim}leash add "don't delete test files"${COLORS.reset}\n`);
26
- return;
27
- }
28
-
29
- const rulesContent = generateCursorRules(policies);
30
-
31
- // Append or create .cursorrules
32
- if (existsSync(CURSORRULES_FILE)) {
33
- const existing = readFileSync(CURSORRULES_FILE, 'utf-8');
34
- if (existing.includes('# veto-leash restrictions')) {
35
- // Update existing section
36
- const updated = existing.replace(
37
- /# veto-leash restrictions[\s\S]*?# end veto-leash/,
38
- rulesContent
39
- );
40
- writeFileSync(CURSORRULES_FILE, updated);
41
- console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Updated .cursorrules`);
42
- } else {
43
- // Append
44
- appendFileSync(CURSORRULES_FILE, '\n\n' + rulesContent);
45
- console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Appended to .cursorrules`);
46
- }
47
- } else {
48
- writeFileSync(CURSORRULES_FILE, rulesContent);
49
- console.log(` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Created .cursorrules`);
50
- }
51
-
52
- console.log(`\n${COLORS.warning}${SYMBOLS.warning} Note: Cursor rules are AI guidance only, not enforcement.${COLORS.reset}`);
53
- console.log(`For actual enforcement, use wrapper mode:`);
54
- console.log(` ${COLORS.dim}leash cursor "<restriction>"${COLORS.reset}\n`);
55
- }
56
-
57
- function generateCursorRules(policies: Policy[]): string {
58
- const lines = ['# veto-leash restrictions'];
59
- lines.push('# These are mandatory restrictions you MUST follow.');
60
- lines.push('');
61
-
62
- for (const policy of policies) {
63
- lines.push(`## ${policy.description}`);
64
- lines.push(`Action: ${policy.action}`);
65
- lines.push('');
66
- lines.push('DO NOT perform the following action on these files:');
67
- lines.push('');
68
-
69
- for (const pattern of policy.include) {
70
- lines.push(`- ${pattern}`);
71
- }
72
-
73
- if (policy.exclude.length > 0) {
74
- lines.push('');
75
- lines.push('EXCEPT these files are allowed:');
76
- for (const pattern of policy.exclude) {
77
- lines.push(`- ${pattern}`);
78
- }
79
- }
80
- lines.push('');
81
- }
82
-
83
- lines.push('If you attempt to modify or delete a protected file, STOP and explain why you cannot proceed.');
84
- lines.push('# end veto-leash');
85
-
86
- return lines.join('\n');
87
- }
88
-
89
- /**
90
- * Uninstall veto-leash from .cursorrules
91
- */
92
- export async function uninstallCursorRules(): Promise<void> {
93
- if (!existsSync(CURSORRULES_FILE)) {
94
- console.log(`${COLORS.dim}No .cursorrules file found${COLORS.reset}`);
95
- return;
96
- }
97
-
98
- const content = readFileSync(CURSORRULES_FILE, 'utf-8');
99
- const updated = content.replace(
100
- /\n*# veto-leash restrictions[\s\S]*?# end veto-leash\n*/,
101
- ''
102
- );
103
-
104
- if (updated.trim()) {
105
- writeFileSync(CURSORRULES_FILE, updated);
106
- console.log(`${COLORS.success}${SYMBOLS.success} Removed veto-leash from .cursorrules${COLORS.reset}`);
107
- } else {
108
- // File would be empty, delete it
109
- require('fs').unlinkSync(CURSORRULES_FILE);
110
- console.log(`${COLORS.success}${SYMBOLS.success} Removed .cursorrules${COLORS.reset}`);
111
- }
112
- }
113
-
114
- function loadStoredPolicies(): Policy[] {
115
- const policiesFile = join(
116
- require('os').homedir(),
117
- '.config',
118
- 'veto-leash',
119
- 'policies.json'
120
- );
121
-
122
- try {
123
- if (existsSync(policiesFile)) {
124
- const data = JSON.parse(readFileSync(policiesFile, 'utf-8'));
125
- return data.policies?.map((p: { policy: Policy }) => p.policy) || [];
126
- }
127
- } catch {
128
- // Ignore
129
- }
130
- return [];
131
- }
@@ -1,233 +0,0 @@
1
- // src/native/index.ts
2
- // Agent registry and unified interface for native integrations
3
-
4
- import type { Policy } from '../types.js';
5
- import { COLORS, SYMBOLS } from '../ui/colors.js';
6
-
7
- // Import all agent integrations
8
- import {
9
- installClaudeCodeHook,
10
- addClaudeCodePolicy,
11
- uninstallClaudeCodeHook,
12
- } from './claude-code.js';
13
- import {
14
- installOpenCodePermissions,
15
- uninstallOpenCodePermissions,
16
- savePolicy as saveOpenCodePolicy,
17
- } from './opencode.js';
18
- import {
19
- installWindsurfHooks,
20
- addWindsurfPolicy,
21
- uninstallWindsurfHooks,
22
- } from './windsurf.js';
23
- import {
24
- installCursorRules,
25
- uninstallCursorRules,
26
- } from './cursor.js';
27
- import {
28
- installAiderConfig,
29
- uninstallAiderConfig,
30
- } from './aider.js';
31
-
32
- export interface AgentInfo {
33
- id: string;
34
- name: string;
35
- aliases: string[];
36
- hasNativeHooks: boolean;
37
- description: string;
38
- }
39
-
40
- export const AGENTS: AgentInfo[] = [
41
- {
42
- id: 'claude-code',
43
- name: 'Claude Code',
44
- aliases: ['cc', 'claude', 'claude-code'],
45
- hasNativeHooks: true,
46
- description: 'PreToolUse hooks for Bash/Write/Edit',
47
- },
48
- {
49
- id: 'opencode',
50
- name: 'OpenCode',
51
- aliases: ['oc', 'opencode'],
52
- hasNativeHooks: true,
53
- description: 'permission.bash rules in opencode.json',
54
- },
55
- {
56
- id: 'windsurf',
57
- name: 'Windsurf',
58
- aliases: ['ws', 'windsurf', 'cascade'],
59
- hasNativeHooks: true,
60
- description: 'Cascade hooks for pre_write_code/pre_run_command',
61
- },
62
- {
63
- id: 'cursor',
64
- name: 'Cursor',
65
- aliases: ['cursor'],
66
- hasNativeHooks: false,
67
- description: '.cursorrules AI guidance (not enforcement)',
68
- },
69
- {
70
- id: 'aider',
71
- name: 'Aider',
72
- aliases: ['aider'],
73
- hasNativeHooks: false,
74
- description: '.aider.conf.yml read-only files',
75
- },
76
- {
77
- id: 'codex',
78
- name: 'Codex CLI',
79
- aliases: ['codex', 'codex-cli'],
80
- hasNativeHooks: false,
81
- description: 'OS sandbox - use watchdog mode',
82
- },
83
- {
84
- id: 'copilot',
85
- name: 'GitHub Copilot',
86
- aliases: ['copilot', 'gh-copilot'],
87
- hasNativeHooks: false,
88
- description: 'No hook system - use wrapper mode',
89
- },
90
- ];
91
-
92
- /**
93
- * Resolve agent alias to agent ID
94
- */
95
- export function resolveAgent(input: string): AgentInfo | null {
96
- const normalized = input?.toLowerCase().trim();
97
- if (!normalized) return null;
98
-
99
- for (const agent of AGENTS) {
100
- if (agent.aliases.includes(normalized)) {
101
- return agent;
102
- }
103
- }
104
- return null;
105
- }
106
-
107
- /**
108
- * Install native integration for an agent
109
- */
110
- export async function installAgent(
111
- agentId: string,
112
- options: { global?: boolean } = {}
113
- ): Promise<boolean> {
114
- const agent = resolveAgent(agentId);
115
-
116
- if (!agent) {
117
- console.error(`${COLORS.error}${SYMBOLS.error} Unknown agent: ${agentId}${COLORS.reset}`);
118
- printSupportedAgents();
119
- return false;
120
- }
121
-
122
- switch (agent.id) {
123
- case 'claude-code':
124
- await installClaudeCodeHook();
125
- return true;
126
-
127
- case 'opencode':
128
- await installOpenCodePermissions(options.global ? 'global' : 'project');
129
- return true;
130
-
131
- case 'windsurf':
132
- await installWindsurfHooks(options.global ? 'user' : 'workspace');
133
- return true;
134
-
135
- case 'cursor':
136
- await installCursorRules();
137
- return true;
138
-
139
- case 'aider':
140
- await installAiderConfig(options.global ? 'global' : 'project');
141
- return true;
142
-
143
- case 'codex':
144
- console.log(`\n${COLORS.warning}${SYMBOLS.warning} Codex CLI uses OS-level sandboxing.${COLORS.reset}`);
145
- console.log(`Use watchdog mode for file protection:`);
146
- console.log(` ${COLORS.dim}leash watch "protect test files"${COLORS.reset}\n`);
147
- return false;
148
-
149
- case 'copilot':
150
- console.log(`\n${COLORS.warning}${SYMBOLS.warning} GitHub Copilot has no hook system.${COLORS.reset}`);
151
- console.log(`Use wrapper mode or watchdog:`);
152
- console.log(` ${COLORS.dim}leash watch "protect .env"${COLORS.reset}\n`);
153
- return false;
154
-
155
- default:
156
- console.error(`${COLORS.error}${SYMBOLS.error} No native integration for ${agent.name}${COLORS.reset}`);
157
- return false;
158
- }
159
- }
160
-
161
- /**
162
- * Uninstall native integration for an agent
163
- */
164
- export async function uninstallAgent(
165
- agentId: string,
166
- options: { global?: boolean } = {}
167
- ): Promise<boolean> {
168
- const agent = resolveAgent(agentId);
169
-
170
- if (!agent) {
171
- console.error(`${COLORS.error}${SYMBOLS.error} Unknown agent: ${agentId}${COLORS.reset}`);
172
- return false;
173
- }
174
-
175
- switch (agent.id) {
176
- case 'claude-code':
177
- await uninstallClaudeCodeHook();
178
- return true;
179
-
180
- case 'opencode':
181
- await uninstallOpenCodePermissions(options.global ? 'global' : 'project');
182
- return true;
183
-
184
- case 'windsurf':
185
- await uninstallWindsurfHooks(options.global ? 'user' : 'workspace');
186
- return true;
187
-
188
- case 'cursor':
189
- await uninstallCursorRules();
190
- return true;
191
-
192
- case 'aider':
193
- await uninstallAiderConfig(options.global ? 'global' : 'project');
194
- return true;
195
-
196
- default:
197
- console.log(`${COLORS.dim}No native integration to remove for ${agent.name}${COLORS.reset}`);
198
- return false;
199
- }
200
- }
201
-
202
- /**
203
- * Add a policy to all installed native integrations
204
- */
205
- export async function addPolicyToAgents(
206
- policy: Policy,
207
- name: string
208
- ): Promise<void> {
209
- // Always save to veto-leash config
210
- saveOpenCodePolicy(name, policy);
211
-
212
- // Claude Code
213
- await addClaudeCodePolicy(policy, name);
214
-
215
- // Windsurf
216
- await addWindsurfPolicy(policy, name);
217
- }
218
-
219
- function printSupportedAgents(): void {
220
- console.log(`\nSupported agents:`);
221
- for (const agent of AGENTS) {
222
- const hookStatus = agent.hasNativeHooks ? COLORS.success + 'native' : COLORS.dim + 'wrapper';
223
- console.log(` ${COLORS.dim}${agent.aliases[0].padEnd(12)}${COLORS.reset} ${agent.name} (${hookStatus}${COLORS.reset})`);
224
- }
225
- console.log('');
226
- }
227
-
228
- // Re-export individual modules
229
- export * from './claude-code.js';
230
- export * from './opencode.js';
231
- export * from './windsurf.js';
232
- export * from './cursor.js';
233
- export * from './aider.js';
@@ -1,310 +0,0 @@
1
- // src/native/opencode.ts
2
- // OpenCode native permission integration
3
- // Generates permission rules for OpenCode's opencode.json config
4
-
5
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
6
- import { join } from 'path';
7
- import { homedir } from 'os';
8
- import type { Policy } from '../types.js';
9
- import { COLORS, SYMBOLS } from '../ui/colors.js';
10
-
11
- const OPENCODE_GLOBAL_CONFIG = join(
12
- homedir(),
13
- '.config',
14
- 'opencode',
15
- 'opencode.json'
16
- );
17
- const OPENCODE_PROJECT_CONFIG = 'opencode.json';
18
- const VETO_LEASH_CONFIG_DIR = join(homedir(), '.config', 'veto-leash');
19
- const POLICIES_FILE = join(VETO_LEASH_CONFIG_DIR, 'policies.json');
20
-
21
- interface OpenCodeConfig {
22
- $schema?: string;
23
- permission?: {
24
- edit?: string | Record<string, string>;
25
- bash?: string | Record<string, string>;
26
- [key: string]: unknown;
27
- };
28
- [key: string]: unknown;
29
- }
30
-
31
- interface StoredPolicies {
32
- policies: Array<{
33
- restriction: string;
34
- policy: Policy;
35
- }>;
36
- }
37
-
38
- /**
39
- * Convert a veto-leash policy to OpenCode permission rules
40
- */
41
- function policyToOpenCodeRules(policy: Policy): Record<string, string> {
42
- const rules: Record<string, string> = {};
43
-
44
- // Generate bash permission rules based on action and patterns
45
- if (policy.action === 'delete') {
46
- // Block rm commands for protected files
47
- for (const pattern of policy.include) {
48
- // Convert glob to OpenCode wildcard format
49
- const ocPattern = pattern
50
- .replace(/\*\*\//g, '*/') // **/ -> */
51
- .replace(/\*\*/g, '*'); // ** -> *
52
-
53
- rules[`rm ${ocPattern}`] = 'deny';
54
- rules[`rm -f ${ocPattern}`] = 'deny';
55
- rules[`rm -rf ${ocPattern}`] = 'deny';
56
- rules[`rm -r ${ocPattern}`] = 'deny';
57
- rules[`git rm ${ocPattern}`] = 'deny';
58
- rules[`git rm -f ${ocPattern}`] = 'deny';
59
- }
60
-
61
- // Allow excluded patterns
62
- for (const pattern of policy.exclude) {
63
- const ocPattern = pattern
64
- .replace(/\*\*\//g, '*/')
65
- .replace(/\*\*/g, '*');
66
-
67
- rules[`rm ${ocPattern}`] = 'allow';
68
- rules[`rm -f ${ocPattern}`] = 'allow';
69
- rules[`rm -rf ${ocPattern}`] = 'allow';
70
- }
71
- }
72
-
73
- if (policy.action === 'modify') {
74
- // Block modification commands for protected files
75
- for (const pattern of policy.include) {
76
- const ocPattern = pattern
77
- .replace(/\*\*\//g, '*/')
78
- .replace(/\*\*/g, '*');
79
-
80
- rules[`mv ${ocPattern} *`] = 'deny';
81
- rules[`cp * ${ocPattern}`] = 'deny';
82
- }
83
- }
84
-
85
- if (policy.action === 'execute') {
86
- // Block execution for protected patterns
87
- for (const pattern of policy.include) {
88
- const ocPattern = pattern
89
- .replace(/\*\*\//g, '*/')
90
- .replace(/\*\*/g, '*');
91
-
92
- // For migrations, block common migration commands
93
- if (pattern.includes('migrat')) {
94
- rules['*migrate*'] = 'deny';
95
- rules['prisma migrate*'] = 'deny';
96
- rules['npx prisma migrate*'] = 'deny';
97
- rules['drizzle-kit *'] = 'deny';
98
- }
99
- }
100
- }
101
-
102
- return rules;
103
- }
104
-
105
- /**
106
- * Install veto-leash permissions into OpenCode config
107
- */
108
- export async function installOpenCodePermissions(
109
- target: 'global' | 'project' = 'project'
110
- ): Promise<void> {
111
- console.log(
112
- `\n${COLORS.info}Installing veto-leash for OpenCode (${target})...${COLORS.reset}\n`
113
- );
114
-
115
- // Load existing policies
116
- const storedPolicies = loadStoredPolicies();
117
- if (storedPolicies.policies.length === 0) {
118
- console.log(
119
- `${COLORS.warning}${SYMBOLS.warning} No policies found. Add policies first:${COLORS.reset}`
120
- );
121
- console.log(` ${COLORS.dim}leash add "don't delete test files"${COLORS.reset}\n`);
122
- return;
123
- }
124
-
125
- // Determine config file path
126
- const configPath =
127
- target === 'global' ? OPENCODE_GLOBAL_CONFIG : OPENCODE_PROJECT_CONFIG;
128
-
129
- // Load existing config
130
- let config: OpenCodeConfig = {
131
- $schema: 'https://opencode.ai/config.json',
132
- };
133
-
134
- if (existsSync(configPath)) {
135
- try {
136
- config = JSON.parse(readFileSync(configPath, 'utf-8'));
137
- } catch {
138
- // Keep default if parse fails
139
- }
140
- }
141
-
142
- // Ensure permission object exists
143
- if (!config.permission) {
144
- config.permission = {};
145
- }
146
-
147
- // Convert existing bash permission to object if it's a string
148
- if (typeof config.permission.bash === 'string') {
149
- const oldValue = config.permission.bash;
150
- config.permission.bash = { '*': oldValue };
151
- } else if (!config.permission.bash) {
152
- config.permission.bash = {};
153
- }
154
-
155
- // Generate and merge rules from all policies
156
- for (const { policy } of storedPolicies.policies) {
157
- const rules = policyToOpenCodeRules(policy);
158
- config.permission.bash = {
159
- ...(config.permission.bash as Record<string, string>),
160
- ...rules,
161
- };
162
- }
163
-
164
- // Write config
165
- if (target === 'global') {
166
- mkdirSync(join(homedir(), '.config', 'opencode'), { recursive: true });
167
- }
168
-
169
- writeFileSync(configPath, JSON.stringify(config, null, 2));
170
- console.log(
171
- ` ${COLORS.success}${SYMBOLS.success}${COLORS.reset} Updated: ${configPath}`
172
- );
173
-
174
- console.log(
175
- `\n${COLORS.success}${SYMBOLS.success} veto-leash permissions installed for OpenCode${COLORS.reset}\n`
176
- );
177
- console.log(`${COLORS.dim}Policies are now enforced via OpenCode's native permission system.${COLORS.reset}`);
178
- console.log(`${COLORS.dim}OpenCode will deny matching commands automatically.${COLORS.reset}\n`);
179
- }
180
-
181
- /**
182
- * Show what permissions would be generated without installing
183
- */
184
- export function previewOpenCodePermissions(): void {
185
- const storedPolicies = loadStoredPolicies();
186
-
187
- if (storedPolicies.policies.length === 0) {
188
- console.log(`\n${COLORS.warning}No policies stored. Add policies first.${COLORS.reset}\n`);
189
- return;
190
- }
191
-
192
- console.log(`\n${COLORS.bold}OpenCode Permission Preview${COLORS.reset}`);
193
- console.log('═'.repeat(30) + '\n');
194
-
195
- const allRules: Record<string, string> = {};
196
-
197
- for (const { restriction, policy } of storedPolicies.policies) {
198
- console.log(`${COLORS.dim}Policy:${COLORS.reset} "${restriction}"`);
199
- console.log(`${COLORS.dim}Action:${COLORS.reset} ${policy.action}\n`);
200
-
201
- const rules = policyToOpenCodeRules(policy);
202
- Object.assign(allRules, rules);
203
- }
204
-
205
- console.log(`${COLORS.bold}Generated Rules:${COLORS.reset}`);
206
- console.log(JSON.stringify({ permission: { bash: allRules } }, null, 2));
207
- console.log(`\n${COLORS.dim}Run 'leash install oc' to apply.${COLORS.reset}\n`);
208
- }
209
-
210
- /**
211
- * Remove veto-leash permissions from OpenCode config
212
- */
213
- export async function uninstallOpenCodePermissions(
214
- target: 'global' | 'project' = 'project'
215
- ): Promise<void> {
216
- const configPath =
217
- target === 'global' ? OPENCODE_GLOBAL_CONFIG : OPENCODE_PROJECT_CONFIG;
218
-
219
- if (!existsSync(configPath)) {
220
- console.log(`${COLORS.dim}No config file found at ${configPath}${COLORS.reset}`);
221
- return;
222
- }
223
-
224
- try {
225
- const config: OpenCodeConfig = JSON.parse(
226
- readFileSync(configPath, 'utf-8')
227
- );
228
-
229
- // Remove veto-leash rules (those with deny for rm/mv/cp patterns)
230
- if (config.permission?.bash && typeof config.permission.bash === 'object') {
231
- const bash = config.permission.bash as Record<string, string>;
232
- for (const key of Object.keys(bash)) {
233
- if (
234
- key.startsWith('rm ') ||
235
- key.startsWith('git rm ') ||
236
- key.startsWith('mv ') ||
237
- key.startsWith('cp ')
238
- ) {
239
- delete bash[key];
240
- }
241
- }
242
- }
243
-
244
- writeFileSync(configPath, JSON.stringify(config, null, 2));
245
- console.log(
246
- `${COLORS.success}${SYMBOLS.success} Removed veto-leash rules from ${configPath}${COLORS.reset}`
247
- );
248
- } catch {
249
- console.log(
250
- `${COLORS.warning}${SYMBOLS.warning} Could not parse config file${COLORS.reset}`
251
- );
252
- }
253
- }
254
-
255
- /**
256
- * Load stored policies from veto-leash config
257
- */
258
- function loadStoredPolicies(): StoredPolicies {
259
- try {
260
- if (existsSync(POLICIES_FILE)) {
261
- return JSON.parse(readFileSync(POLICIES_FILE, 'utf-8'));
262
- }
263
- } catch {
264
- // Return empty if can't load
265
- }
266
- return { policies: [] };
267
- }
268
-
269
- /**
270
- * Save a policy to the stored policies file
271
- */
272
- export function savePolicy(restriction: string, policy: Policy): void {
273
- const stored = loadStoredPolicies();
274
-
275
- // Check for duplicate
276
- const existingIndex = stored.policies.findIndex(
277
- (p) => p.restriction === restriction
278
- );
279
-
280
- if (existingIndex >= 0) {
281
- stored.policies[existingIndex] = { restriction, policy };
282
- } else {
283
- stored.policies.push({ restriction, policy });
284
- }
285
-
286
- mkdirSync(VETO_LEASH_CONFIG_DIR, { recursive: true });
287
- writeFileSync(POLICIES_FILE, JSON.stringify(stored, null, 2));
288
- }
289
-
290
- /**
291
- * List all stored policies
292
- */
293
- export function listPolicies(): void {
294
- const stored = loadStoredPolicies();
295
-
296
- if (stored.policies.length === 0) {
297
- console.log(`\n${COLORS.dim}No policies stored.${COLORS.reset}\n`);
298
- return;
299
- }
300
-
301
- console.log(`\n${COLORS.bold}Stored Policies${COLORS.reset}`);
302
- console.log('═'.repeat(20) + '\n');
303
-
304
- for (let i = 0; i < stored.policies.length; i++) {
305
- const { restriction, policy } = stored.policies[i];
306
- console.log(`${i + 1}. ${COLORS.info}"${restriction}"${COLORS.reset}`);
307
- console.log(` ${COLORS.dim}Action:${COLORS.reset} ${policy.action}`);
308
- console.log(` ${COLORS.dim}Description:${COLORS.reset} ${policy.description}\n`);
309
- }
310
- }