deliberate 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.
- package/LICENSE +11 -0
- package/README.md +180 -0
- package/bin/cli.js +113 -0
- package/hooks/__pycache__/deliberate-commands.cpython-312.pyc +0 -0
- package/hooks/deliberate-changes.py +606 -0
- package/hooks/deliberate-commands-post.py +126 -0
- package/hooks/deliberate-commands.py +1742 -0
- package/hooks/hooks.json +29 -0
- package/hooks/setup-check.py +67 -0
- package/hooks/test_skip_commands.py +293 -0
- package/package.json +51 -0
- package/src/classifier/classify_command.py +346 -0
- package/src/classifier/embed_command.py +56 -0
- package/src/classifier/index.js +324 -0
- package/src/classifier/model-classifier.js +531 -0
- package/src/classifier/pattern-matcher.js +230 -0
- package/src/config.js +207 -0
- package/src/index.js +23 -0
- package/src/install.js +754 -0
- package/src/server.js +239 -0
- package/src/uninstall.js +198 -0
- package/training/build_classifier.py +325 -0
- package/training/expanded-command-safety.jsonl +712 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Matcher - Layer 1 of the classifier
|
|
3
|
+
* Deterministic regex-based detection that cannot be prompt-injected.
|
|
4
|
+
* This is the authoritative layer - if a pattern matches, the result is final.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Known dangerous command patterns
|
|
8
|
+
// NOTE: Patterns are checked in order. More specific patterns should come first.
|
|
9
|
+
// canOverride: false = hard block (catastrophic), true = warn but allow user override
|
|
10
|
+
const DANGEROUS_PATTERNS = [
|
|
11
|
+
// File system destruction - CATASTROPHIC (no override)
|
|
12
|
+
{ pattern: /\brm\s+-rf\s+\/\s*$/, risk: 'DANGEROUS', reason: 'Recursive deletion of root filesystem', canOverride: false },
|
|
13
|
+
{ pattern: /\brm\s+-rf\s+~\/?\s*$/, risk: 'DANGEROUS', reason: 'Recursive deletion of home directory', canOverride: false },
|
|
14
|
+
{ pattern: /\brm\s+-rf\s+\/\*/, risk: 'DANGEROUS', reason: 'Recursive deletion of root filesystem', canOverride: false },
|
|
15
|
+
{ pattern: /\brm\s+-rf\s+~\/\*\s*$/, risk: 'DANGEROUS', reason: 'Recursive deletion of entire home directory', canOverride: false },
|
|
16
|
+
// File system destruction - DANGEROUS but overridable (specific paths/files)
|
|
17
|
+
{ pattern: /\brm\s+-rf\s+/, risk: 'DANGEROUS', reason: 'Force recursive deletion', canOverride: true },
|
|
18
|
+
{ pattern: /\brm\s+(-[rf]+\s+)*[\/~]/, risk: 'DANGEROUS', reason: 'File deletion from root or home', canOverride: true },
|
|
19
|
+
{ pattern: /\bmkfs\b/, risk: 'DANGEROUS', reason: 'Filesystem formatting' },
|
|
20
|
+
{ pattern: /\bdd\s+if=.*of=\/dev\//, risk: 'DANGEROUS', reason: 'Direct disk write' },
|
|
21
|
+
|
|
22
|
+
// Privilege escalation
|
|
23
|
+
{ pattern: /\bsudo\s+/, risk: 'MODERATE', reason: 'Elevated privileges requested' },
|
|
24
|
+
{ pattern: /\bsu\s+-?\s*$/, risk: 'MODERATE', reason: 'Switch to root user' },
|
|
25
|
+
{ pattern: /\bchmod\s+777\b/, risk: 'DANGEROUS', reason: 'World-writable permissions' },
|
|
26
|
+
{ pattern: /\bchmod\s+\+s\b/, risk: 'DANGEROUS', reason: 'SetUID/SetGID bit' },
|
|
27
|
+
{ pattern: /\bchown\s+root\b/, risk: 'MODERATE', reason: 'Changing ownership to root' },
|
|
28
|
+
|
|
29
|
+
// Network exfiltration
|
|
30
|
+
{ pattern: /\bcurl\s+.*\|\s*(ba)?sh/, risk: 'DANGEROUS', reason: 'Remote code execution via curl pipe' },
|
|
31
|
+
{ pattern: /\bwget\s+.*\|\s*(ba)?sh/, risk: 'DANGEROUS', reason: 'Remote code execution via wget pipe' },
|
|
32
|
+
{ pattern: /\bcurl\s+.*(-d|--data).*\$\(/, risk: 'DANGEROUS', reason: 'Data exfiltration via curl' },
|
|
33
|
+
{ pattern: /\bnc\s+-e\s+\/bin\/(ba)?sh/, risk: 'DANGEROUS', reason: 'Reverse shell' },
|
|
34
|
+
|
|
35
|
+
// Credential access
|
|
36
|
+
{ pattern: /\bcat\s+.*\.(pem|key|passwd|shadow)/, risk: 'DANGEROUS', reason: 'Reading sensitive credentials' },
|
|
37
|
+
{ pattern: /\/\.ssh\/id_rsa/, risk: 'DANGEROUS', reason: 'Accessing SSH private key' },
|
|
38
|
+
{ pattern: /\/\.aws\/credentials/, risk: 'DANGEROUS', reason: 'Accessing AWS credentials' },
|
|
39
|
+
{ pattern: /\/\.env\b/, risk: 'MODERATE', reason: 'Accessing environment secrets' },
|
|
40
|
+
|
|
41
|
+
// Token and key file access (cat, less, more, head, tail, etc.)
|
|
42
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*token/i, risk: 'DANGEROUS', reason: 'Reading token file' },
|
|
43
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*secret/i, risk: 'DANGEROUS', reason: 'Reading secrets file' },
|
|
44
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*api[_-]?key/i, risk: 'DANGEROUS', reason: 'Reading API key file' },
|
|
45
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*\.key\b/, risk: 'DANGEROUS', reason: 'Reading key file' },
|
|
46
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*credential/i, risk: 'DANGEROUS', reason: 'Reading credentials file' },
|
|
47
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*password/i, risk: 'DANGEROUS', reason: 'Reading password file' },
|
|
48
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*\.pem\b/, risk: 'DANGEROUS', reason: 'Reading PEM certificate/key' },
|
|
49
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*private/i, risk: 'DANGEROUS', reason: 'Reading private key file' },
|
|
50
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*oauth/i, risk: 'DANGEROUS', reason: 'Reading OAuth token file' },
|
|
51
|
+
{ pattern: /\b(cat|less|more|head|tail|bat|view)\s+.*bearer/i, risk: 'DANGEROUS', reason: 'Reading bearer token file' },
|
|
52
|
+
|
|
53
|
+
// Process/system manipulation
|
|
54
|
+
{ pattern: /\bkill\s+-9\s+(-1|1)\b/, risk: 'DANGEROUS', reason: 'Killing all processes' },
|
|
55
|
+
{ pattern: /\bkillall\b/, risk: 'MODERATE', reason: 'Killing processes by name' },
|
|
56
|
+
{ pattern: /:()\{\s*:\|:&\s*\};:/, risk: 'DANGEROUS', reason: 'Fork bomb detected' },
|
|
57
|
+
|
|
58
|
+
// Database destruction
|
|
59
|
+
{ pattern: /\bDROP\s+(DATABASE|TABLE)\b/i, risk: 'DANGEROUS', reason: 'Database/table deletion' },
|
|
60
|
+
{ pattern: /\bDELETE\s+FROM\s+\w+\s*(;|$)/i, risk: 'DANGEROUS', reason: 'Unconditional DELETE' },
|
|
61
|
+
{ pattern: /\bTRUNCATE\s+TABLE\b/i, risk: 'DANGEROUS', reason: 'Table truncation' },
|
|
62
|
+
|
|
63
|
+
// Cloud infrastructure - AWS
|
|
64
|
+
{ pattern: /\baws\s+.*\s+delete\b/, risk: 'DANGEROUS', reason: 'AWS resource deletion' },
|
|
65
|
+
{ pattern: /\baws\s+s3\s+rm\b/, risk: 'DANGEROUS', reason: 'S3 object/bucket deletion' },
|
|
66
|
+
{ pattern: /\baws\s+s3\s+sync\b/, risk: 'MODERATE', reason: 'S3 data sync (potential exfiltration)' },
|
|
67
|
+
{ pattern: /\baws\s+ec2\s+terminate-instances\b/, risk: 'DANGEROUS', reason: 'EC2 instance termination' },
|
|
68
|
+
{ pattern: /\baws\s+.*--cidr\s+0\.0\.0\.0\/0/, risk: 'DANGEROUS', reason: 'Opening to all IPs (0.0.0.0/0)' },
|
|
69
|
+
{ pattern: /\baws\s+secretsmanager\s+get-secret-value\b/, risk: 'MODERATE', reason: 'Reading secrets' },
|
|
70
|
+
{ pattern: /\baws\s+ssm\s+get-parameter.*--with-decryption\b/, risk: 'MODERATE', reason: 'Reading encrypted parameters' },
|
|
71
|
+
{ pattern: /\baws\s+iam\s+(create-user|create-access-key|attach-.*-policy)\b/, risk: 'DANGEROUS', reason: 'IAM privilege escalation' },
|
|
72
|
+
{ pattern: /\baws\s+s3api\s+put-bucket-policy\b/, risk: 'DANGEROUS', reason: 'Modifying bucket permissions' },
|
|
73
|
+
{ pattern: /\baws\s+lambda\s+update-function-code\b/, risk: 'MODERATE', reason: 'Lambda code deployment' },
|
|
74
|
+
|
|
75
|
+
// Cloud infrastructure - Terraform/K8s
|
|
76
|
+
{ pattern: /\bterraform\s+destroy\b/, risk: 'DANGEROUS', reason: 'Infrastructure destruction' },
|
|
77
|
+
{ pattern: /\bkubectl\s+delete\s+(namespace|ns)\b/, risk: 'DANGEROUS', reason: 'Kubernetes namespace deletion' },
|
|
78
|
+
{ pattern: /\bkubectl\s+exec\b/, risk: 'MODERATE', reason: 'Kubernetes pod shell access' },
|
|
79
|
+
|
|
80
|
+
// Docker container escapes
|
|
81
|
+
{ pattern: /\bdocker\s+run\s+.*-v\s+\/[^:]*:/, risk: 'DANGEROUS', reason: 'Docker host filesystem mount' },
|
|
82
|
+
{ pattern: /\bdocker\s+run\s+.*--privileged/, risk: 'DANGEROUS', reason: 'Docker privileged mode' },
|
|
83
|
+
|
|
84
|
+
// Reverse shells (expanded)
|
|
85
|
+
{ pattern: /\/dev\/tcp\//, risk: 'DANGEROUS', reason: 'Bash /dev/tcp reverse shell' },
|
|
86
|
+
{ pattern: /\bncat\s+-e\s+/, risk: 'DANGEROUS', reason: 'Ncat reverse shell' },
|
|
87
|
+
{ pattern: /socket.*connect.*dup2.*subprocess/s, risk: 'DANGEROUS', reason: 'Python reverse shell pattern' },
|
|
88
|
+
|
|
89
|
+
// Remote code execution (expanded)
|
|
90
|
+
{ pattern: /\bbash\s+-c\s+.*\$\(curl/, risk: 'DANGEROUS', reason: 'Curl subshell execution' },
|
|
91
|
+
{ pattern: /\bbash\s+-c\s+.*\$\(wget/, risk: 'DANGEROUS', reason: 'Wget subshell execution' },
|
|
92
|
+
|
|
93
|
+
// Obfuscation
|
|
94
|
+
{ pattern: /base64\s+(-d|--decode).*\|\s*(ba)?sh/, risk: 'DANGEROUS', reason: 'Base64 decoded shell execution' },
|
|
95
|
+
{ pattern: /\bxxd\s+-r.*\|\s*(ba)?sh/, risk: 'DANGEROUS', reason: 'Hex decoded shell execution' },
|
|
96
|
+
|
|
97
|
+
// Data exfiltration
|
|
98
|
+
{ pattern: /\benv\b.*\|\s*curl/, risk: 'DANGEROUS', reason: 'Environment variable exfiltration' },
|
|
99
|
+
{ pattern: /\bscp\s+.*\.ssh/, risk: 'DANGEROUS', reason: 'SSH key exfiltration' },
|
|
100
|
+
{ pattern: /\bscp\s+.*\.(pem|key|crt)/, risk: 'DANGEROUS', reason: 'Certificate/key exfiltration' },
|
|
101
|
+
|
|
102
|
+
// Git credential manipulation
|
|
103
|
+
{ pattern: /\bgit\s+config\s+.*credential/, risk: 'MODERATE', reason: 'Git credential configuration' },
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
// Known safe command patterns (skip LLM analysis)
|
|
107
|
+
const SAFE_PATTERNS = [
|
|
108
|
+
{ pattern: /^\s*ls(\s+-[alh]+)*\s*$/, reason: 'List directory contents' },
|
|
109
|
+
{ pattern: /^\s*pwd\s*$/, reason: 'Print working directory' },
|
|
110
|
+
{ pattern: /^\s*echo\s+[^|;&`$]+$/, reason: 'Echo text (simple, no pipes/subshells)' },
|
|
111
|
+
{ pattern: /^\s*cat\s+[^|;&]+\.(txt|md|json|ya?ml|js|ts|py)(\s|$)/, reason: 'View text file' },
|
|
112
|
+
{ pattern: /^\s*head\s+/, reason: 'View file head' },
|
|
113
|
+
{ pattern: /^\s*tail\s+/, reason: 'View file tail' },
|
|
114
|
+
{ pattern: /^\s*wc\s+/, reason: 'Word count' },
|
|
115
|
+
{ pattern: /^\s*which\s+/, reason: 'Locate command' },
|
|
116
|
+
{ pattern: /^\s*whoami\s*$/, reason: 'Current user' },
|
|
117
|
+
{ pattern: /^\s*date\s*$/, reason: 'Current date' },
|
|
118
|
+
{ pattern: /^\s*git\s+(status|log|diff|branch|show)\b/, reason: 'Git read operation' },
|
|
119
|
+
{ pattern: /^\s*npm\s+(list|outdated|--version)\b/, reason: 'NPM info command' },
|
|
120
|
+
{ pattern: /^\s*node\s+--version\s*$/, reason: 'Node version check' },
|
|
121
|
+
{ pattern: /^\s*python3?\s+--version\s*$/, reason: 'Python version check' },
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
// File patterns for Write/Edit analysis
|
|
125
|
+
const DANGEROUS_FILE_PATTERNS = [
|
|
126
|
+
{ pattern: /\.(pem|key|crt|pfx)$/, risk: 'DANGEROUS', reason: 'Certificate/key file' },
|
|
127
|
+
{ pattern: /^\.env/, risk: 'MODERATE', reason: 'Environment configuration' },
|
|
128
|
+
{ pattern: /credentials|secrets?|password/i, risk: 'MODERATE', reason: 'Potential secrets file' },
|
|
129
|
+
{ pattern: /\/etc\//, risk: 'DANGEROUS', reason: 'System configuration' },
|
|
130
|
+
{ pattern: /\/usr\/bin\/|\/usr\/local\/bin\//, risk: 'DANGEROUS', reason: 'System binary location' },
|
|
131
|
+
{ pattern: /\.bashrc|\.zshrc|\.profile/, risk: 'MODERATE', reason: 'Shell configuration' },
|
|
132
|
+
{ pattern: /crontab|\.cron/, risk: 'MODERATE', reason: 'Scheduled task configuration' },
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
// Content patterns for Write/Edit analysis
|
|
136
|
+
const DANGEROUS_CONTENT_PATTERNS = [
|
|
137
|
+
{ pattern: /eval\s*\(.*\$/, risk: 'DANGEROUS', reason: 'Dynamic code execution' },
|
|
138
|
+
{ pattern: /exec\s*\(/, risk: 'MODERATE', reason: 'Process execution' },
|
|
139
|
+
{ pattern: /subprocess\.(run|call|Popen)/, risk: 'MODERATE', reason: 'Subprocess execution' },
|
|
140
|
+
{ pattern: /os\.system\s*\(/, risk: 'MODERATE', reason: 'System command execution' },
|
|
141
|
+
{ pattern: /child_process/, risk: 'MODERATE', reason: 'Child process spawning' },
|
|
142
|
+
{ pattern: /dangerouslySetInnerHTML/, risk: 'MODERATE', reason: 'XSS-prone React pattern' },
|
|
143
|
+
{ pattern: /innerHTML\s*=/, risk: 'MODERATE', reason: 'Potential XSS vector' },
|
|
144
|
+
{ pattern: /SELECT\s+.*\+\s*['"]?\s*\+/, risk: 'DANGEROUS', reason: 'SQL injection pattern' },
|
|
145
|
+
{ pattern: /password\s*[:=]\s*['"][^'"]+['"]/, risk: 'DANGEROUS', reason: 'Hardcoded password' },
|
|
146
|
+
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]+['"]/, risk: 'DANGEROUS', reason: 'Hardcoded API key' },
|
|
147
|
+
{ pattern: /BEGIN\s+(RSA|DSA|EC|OPENSSH)\s+PRIVATE\s+KEY/, risk: 'DANGEROUS', reason: 'Private key in code' },
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
export class PatternMatcher {
|
|
151
|
+
/**
|
|
152
|
+
* Check a command against known patterns
|
|
153
|
+
* @param {string} command - The command to check
|
|
154
|
+
* @returns {{ matched: boolean, risk?: string, reason?: string, source: string }}
|
|
155
|
+
*/
|
|
156
|
+
checkCommand(command) {
|
|
157
|
+
// Check dangerous patterns first (order matters - more specific patterns first)
|
|
158
|
+
for (const { pattern, risk, reason, canOverride } of DANGEROUS_PATTERNS) {
|
|
159
|
+
if (pattern.test(command)) {
|
|
160
|
+
return {
|
|
161
|
+
matched: true,
|
|
162
|
+
risk,
|
|
163
|
+
reason,
|
|
164
|
+
source: 'pattern',
|
|
165
|
+
canOverride: canOverride !== undefined ? canOverride : false // Default to false if not specified
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check safe patterns
|
|
171
|
+
for (const { pattern, reason } of SAFE_PATTERNS) {
|
|
172
|
+
if (pattern.test(command)) {
|
|
173
|
+
return {
|
|
174
|
+
matched: true,
|
|
175
|
+
risk: 'SAFE',
|
|
176
|
+
reason,
|
|
177
|
+
source: 'pattern',
|
|
178
|
+
canOverride: false
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// No pattern match - needs classifier
|
|
184
|
+
return { matched: false, source: 'pattern' };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check a file path against known patterns
|
|
189
|
+
* @param {string} filePath - The file path to check
|
|
190
|
+
* @returns {{ matched: boolean, risk?: string, reason?: string, source: string }}
|
|
191
|
+
*/
|
|
192
|
+
checkFilePath(filePath) {
|
|
193
|
+
for (const { pattern, risk, reason } of DANGEROUS_FILE_PATTERNS) {
|
|
194
|
+
if (pattern.test(filePath)) {
|
|
195
|
+
return {
|
|
196
|
+
matched: true,
|
|
197
|
+
risk,
|
|
198
|
+
reason,
|
|
199
|
+
source: 'pattern',
|
|
200
|
+
canOverride: false
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { matched: false, source: 'pattern' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check file content against known patterns
|
|
210
|
+
* @param {string} content - The file content to check
|
|
211
|
+
* @returns {{ matched: boolean, risk?: string, reason?: string, source: string }}
|
|
212
|
+
*/
|
|
213
|
+
checkContent(content) {
|
|
214
|
+
for (const { pattern, risk, reason } of DANGEROUS_CONTENT_PATTERNS) {
|
|
215
|
+
if (pattern.test(content)) {
|
|
216
|
+
return {
|
|
217
|
+
matched: true,
|
|
218
|
+
risk,
|
|
219
|
+
reason,
|
|
220
|
+
source: 'pattern',
|
|
221
|
+
canOverride: false
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { matched: false, source: 'pattern' };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default PatternMatcher;
|
package/src/config.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Deliberate Claude Code
|
|
3
|
+
* Stores user preferences in ~/.deliberate/config.json
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
|
|
10
|
+
// Cross-platform home directory
|
|
11
|
+
const HOME_DIR = os.homedir();
|
|
12
|
+
const DELIBERATE_DIR = path.join(HOME_DIR, '.deliberate');
|
|
13
|
+
const CONFIG_FILE = path.join(DELIBERATE_DIR, 'config.json');
|
|
14
|
+
|
|
15
|
+
// Default configuration
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
llm: {
|
|
18
|
+
provider: null, // 'claude-subscription', 'anthropic', 'ollama', or null
|
|
19
|
+
apiKey: null, // For 'anthropic' provider
|
|
20
|
+
baseUrl: null, // Custom URL (e.g., Ollama endpoint)
|
|
21
|
+
model: null // Model to use
|
|
22
|
+
},
|
|
23
|
+
classifier: {
|
|
24
|
+
serverPort: 8765,
|
|
25
|
+
enabled: true
|
|
26
|
+
},
|
|
27
|
+
blocking: {
|
|
28
|
+
enabled: false, // When true, auto-block high-confidence DANGEROUS operations
|
|
29
|
+
confidenceThreshold: 0.85 // Block if DANGEROUS + confidence > this threshold
|
|
30
|
+
},
|
|
31
|
+
deduplication: {
|
|
32
|
+
enabled: true // When true, don't show same warning twice per session
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Provider configurations
|
|
37
|
+
export const LLM_PROVIDERS = {
|
|
38
|
+
'claude-subscription': {
|
|
39
|
+
name: 'Claude Pro/Max Subscription',
|
|
40
|
+
description: 'Use your Claude Pro or Max subscription (recommended)',
|
|
41
|
+
baseUrl: 'https://api.anthropic.com/v1/messages',
|
|
42
|
+
model: 'claude-sonnet-4-20250514',
|
|
43
|
+
requiresApiKey: false, // Uses OAuth token
|
|
44
|
+
usesOAuth: true
|
|
45
|
+
},
|
|
46
|
+
anthropic: {
|
|
47
|
+
name: 'Anthropic API Key',
|
|
48
|
+
description: 'Use your Anthropic API key directly (pay-per-token)',
|
|
49
|
+
baseUrl: 'https://api.anthropic.com/v1/messages',
|
|
50
|
+
model: 'claude-3-5-haiku-20241022',
|
|
51
|
+
requiresApiKey: true
|
|
52
|
+
},
|
|
53
|
+
ollama: {
|
|
54
|
+
name: 'Ollama (local)',
|
|
55
|
+
description: 'Use a local Ollama model (free, private)',
|
|
56
|
+
baseUrl: 'http://localhost:11434/api/generate',
|
|
57
|
+
model: 'llama3.2',
|
|
58
|
+
requiresApiKey: false
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Ensure the .deliberate directory exists
|
|
64
|
+
*/
|
|
65
|
+
function ensureDir() {
|
|
66
|
+
if (!fs.existsSync(DELIBERATE_DIR)) {
|
|
67
|
+
fs.mkdirSync(DELIBERATE_DIR, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Load configuration from disk
|
|
73
|
+
* @returns {Object} Configuration object
|
|
74
|
+
*/
|
|
75
|
+
export function loadConfig() {
|
|
76
|
+
try {
|
|
77
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
78
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
79
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn('Warning: Could not load config, using defaults');
|
|
83
|
+
}
|
|
84
|
+
return { ...DEFAULT_CONFIG };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Save configuration to disk
|
|
89
|
+
* @param {Object} config - Configuration object
|
|
90
|
+
*/
|
|
91
|
+
export function saveConfig(config) {
|
|
92
|
+
ensureDir();
|
|
93
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get LLM configuration for hooks
|
|
98
|
+
* Returns environment-variable-friendly format
|
|
99
|
+
* @returns {Object} LLM config with provider, url, key, model
|
|
100
|
+
*/
|
|
101
|
+
export function getLLMConfig() {
|
|
102
|
+
const config = loadConfig();
|
|
103
|
+
const llm = config.llm;
|
|
104
|
+
|
|
105
|
+
if (!llm.provider) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const providerConfig = LLM_PROVIDERS[llm.provider];
|
|
110
|
+
if (!providerConfig) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
provider: llm.provider,
|
|
116
|
+
baseUrl: llm.baseUrl || providerConfig.baseUrl,
|
|
117
|
+
apiKey: llm.apiKey,
|
|
118
|
+
model: llm.model || providerConfig.model
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Set LLM provider configuration
|
|
124
|
+
* @param {string} provider - Provider name
|
|
125
|
+
* @param {Object} options - Additional options (apiKey, baseUrl, model)
|
|
126
|
+
*/
|
|
127
|
+
export function setLLMProvider(provider, options = {}) {
|
|
128
|
+
const config = loadConfig();
|
|
129
|
+
|
|
130
|
+
if (!LLM_PROVIDERS[provider]) {
|
|
131
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const providerConfig = LLM_PROVIDERS[provider];
|
|
135
|
+
|
|
136
|
+
config.llm = {
|
|
137
|
+
provider,
|
|
138
|
+
apiKey: options.apiKey || null,
|
|
139
|
+
baseUrl: options.baseUrl || providerConfig.baseUrl,
|
|
140
|
+
model: options.model || providerConfig.model
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
saveConfig(config);
|
|
144
|
+
return config.llm;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if LLM is configured
|
|
149
|
+
* @returns {boolean}
|
|
150
|
+
*/
|
|
151
|
+
export function isLLMConfigured() {
|
|
152
|
+
const config = loadConfig();
|
|
153
|
+
return !!config.llm.provider;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get the config directory path
|
|
158
|
+
* @returns {string}
|
|
159
|
+
*/
|
|
160
|
+
export function getConfigDir() {
|
|
161
|
+
return DELIBERATE_DIR;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the config file path
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
export function getConfigFile() {
|
|
169
|
+
return CONFIG_FILE;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get blocking configuration
|
|
174
|
+
* @returns {Object} { enabled: boolean, confidenceThreshold: number }
|
|
175
|
+
*/
|
|
176
|
+
export function getBlockingConfig() {
|
|
177
|
+
const config = loadConfig();
|
|
178
|
+
return config.blocking || DEFAULT_CONFIG.blocking;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Set blocking configuration
|
|
183
|
+
* @param {boolean} enabled - Whether auto-blocking is enabled
|
|
184
|
+
* @param {number} confidenceThreshold - Threshold for auto-blocking (0-1)
|
|
185
|
+
*/
|
|
186
|
+
export function setBlockingConfig(enabled, confidenceThreshold = 0.85) {
|
|
187
|
+
const config = loadConfig();
|
|
188
|
+
config.blocking = {
|
|
189
|
+
enabled: !!enabled,
|
|
190
|
+
confidenceThreshold: Math.max(0, Math.min(1, confidenceThreshold))
|
|
191
|
+
};
|
|
192
|
+
saveConfig(config);
|
|
193
|
+
return config.blocking;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default {
|
|
197
|
+
loadConfig,
|
|
198
|
+
saveConfig,
|
|
199
|
+
getLLMConfig,
|
|
200
|
+
setLLMProvider,
|
|
201
|
+
isLLMConfigured,
|
|
202
|
+
getConfigDir,
|
|
203
|
+
getConfigFile,
|
|
204
|
+
getBlockingConfig,
|
|
205
|
+
setBlockingConfig,
|
|
206
|
+
LLM_PROVIDERS
|
|
207
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deliberate/claude-code
|
|
3
|
+
* Security-focused command and file change explanations for Claude Code
|
|
4
|
+
*
|
|
5
|
+
* Main exports for programmatic use
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Classifier exports
|
|
9
|
+
export {
|
|
10
|
+
classify,
|
|
11
|
+
quickCheck,
|
|
12
|
+
getStatus,
|
|
13
|
+
preloadModel,
|
|
14
|
+
initialize,
|
|
15
|
+
PatternMatcher,
|
|
16
|
+
ModelClassifier
|
|
17
|
+
} from './classifier/index.js';
|
|
18
|
+
|
|
19
|
+
// Server exports
|
|
20
|
+
export { startServer } from './server.js';
|
|
21
|
+
|
|
22
|
+
// Installer exports
|
|
23
|
+
export { install } from './install.js';
|