@hynaweb/kavlr 1.0.0 โ†’ 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 (3) hide show
  1. package/README.md +30 -0
  2. package/bin/cli.js +173 -86
  3. package/package.json +5 -9
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # ๐Ÿ›ก๏ธ KAVLR: The Agent Leash
2
+ **Runtime security for AI coding agents. Built on a $150 phone.**
3
+
4
+ KAVLR is a fail-closed Bash hook that intercepts dangerous terminal commands (like `rm -rf`, `cat .env`, `sudo`) executed by AI agents (Claude Code, Cursor, Devin) *before* the OS runs them.
5
+
6
+ If the API is unreachable, it defaults to **BLOCKED**. Safety first. Always.
7
+
8
+ ### โšก Install in 30 Seconds
9
+ ```bash
10
+ npm install -g @hynaweb/kavlr
11
+ javlr init
12
+ ```
13
+ *(Paste your API key from [kavlr.netlify.app](https://kavlr.netlify.app) when prompted)*
14
+
15
+ ### ๐Ÿงช Test It
16
+ Try to read your environment variables:
17
+ ```bash
18
+ cat .env
19
+ ```
20
+ **Expected Output:** `๐Ÿ›  [BLOCKED] THREAT DETECTED`
21
+
22
+ *(Pro Tip: Need to bypass it for debugging? Just use a backslash: `\cat .env`)*
23
+
24
+ ### ๐Ÿฅ– Why I Built This
25
+ Iโ€™m a 12-year-old developer building entirely on a $150 Android phone. I realized AI agents can halucinate and destroy production environments with zero "Are you sure?" prompts. Enterprise tools cost thousands. KAVLR is free, lightweight, and fail-closed.
26
+
27
+ [Read the full story of how my own code locked me out for 48 hours โ†’](https://kavlr.netlify.app) *(Link to your Dev.to post later, or a Notion doc for now)*
28
+
29
+ ---
30
+ **Tech Stack:** Vanilla JS, Supabase Edge Functions (Deno), PostgreSQL (RLS), Bash.
package/bin/cli.js CHANGED
@@ -1,106 +1,193 @@
1
1
  #!/usr/bin/env node
2
- const axios = require('axios');
2
+
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
- const { execSync } = require('child_process');
7
- const readline = require('readline');
8
-
9
- const API_URL = 'https://yasllkvdrgnqtjesdyfz.supabase.co/functions/v1/intercept';
10
- const args = process.argv.slice(2);
11
- const command = args[0];
12
-
13
- if (command === 'init') {
14
- console.log('\x1b[36m%s\x1b[0m', '๐Ÿ›ก๏ธ Initializing KAVLR...');
15
- const shell = process.env.SHELL || '';
16
- const rcFile = shell.includes('zsh') ? '.zshrc' : '.bashrc';
17
- const rcPath = path.join(os.homedir(), rcFile);
18
-
19
- const rl = readline.createInterface({
20
- input: process.stdin,
21
- output: process.stdout
22
- });
23
-
24
- rl.question('\n\x1b[33mPlease enter your KAVLR API Key: \x1b[0m', (apiKey) => {
25
- if (!apiKey) {
26
- console.log('\x1b[31m%s\x1b[0m', 'โŒ API Key is required. Setup aborted.');
27
- rl.close();
28
- return;
29
- }
6
+ const { execSync, spawn } = require('child_process');
7
+ const chalk = require('chalk');
8
+ const { program } = require('commander');
30
9
 
31
- const configStr = `\n# KAVLR Agent Leash\nexport KAVLR_API_KEY="${apiKey}"\nalias rm="kavlr hook rm"\nalias cat="kavlr hook cat"\nalias cp="kavlr hook cp"\nalias mv="kavlr hook mv"\n`;
10
+ const CONFIG_FILE = path.join(os.homedir(), '.kavlr_config');
11
+ const SUPABASE_URL = "https://yasllkvdrgnqtjesdyfz.supabase.co";
12
+ const SUPABASE_ANON = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inlhc2xsa3ZkcmducXRqZXNkeWZ6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3OTIyODQwNjgsImV4cCI6MjEwNzg2MDA2OH0.1bD80IRX7agRAK7luYzdsUvFO3uSbfaK1OSo58l4-N4";
32
13
 
33
- if (fs.existsSync(rcPath)) {
34
- const content = fs.readFileSync(rcPath, 'utf8');
35
- if (content.includes('KAVLR Agent Leash') === false) {
36
- fs.appendFileSync(rcPath, configStr);
37
- console.log('\n\x1b[32m%s\x1b[0m', 'โœ… API Key and Hook installed in ~/' + rcFile);
38
- console.log('\x1b[36m%s\x1b[0m', '๐Ÿ‘‰ Run: source ~/' + rcFile + ' to activate.');
39
- } else {
40
- console.log('\n\x1b[32m%s\x1b[0m', 'โœ… KAVLR is already configured. (If you need to change your key, edit ~/' + rcFile + ')');
41
- }
14
+ function loadKey() {
15
+ if (process.env.KAVLR_API_KEY) return process.env.KAVLR_API_KEY;
16
+ if (fs.existsSync(CONFIG_FILE)) {
17
+ try {
18
+ const data = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
19
+ return data.api_key;
20
+ } catch (e) {}
42
21
  }
43
- rl.close();
44
- });
45
-
46
- return;
22
+ return null;
47
23
  }
48
24
 
49
- if (command === 'hook') {
50
- const originalCmd = args[1];
51
- const originalArgs = args.slice(2).join(' ');
52
- const payload = originalCmd + ' ' + originalArgs;
53
-
54
- analyzeCommand(payload, true, originalCmd, originalArgs, true);
55
- return;
25
+ function saveKey(key) {
26
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify({ api_key: key }), { mode: 0o600 });
27
+ console.log(chalk.green(`โœ… API Key saved securely to ${CONFIG_FILE}`));
56
28
  }
57
29
 
58
- if (command === 'run') {
59
- const payload = args.slice(1).join(' ');
60
- analyzeCommand(payload, false, null, null, false);
61
- return;
30
+ function printLogo() {
31
+ console.log(chalk.cyan.bold(`
32
+ โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—
33
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—
34
+ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•
35
+ โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—
36
+ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ• โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘
37
+ โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ• โ•šโ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•
38
+ `) + chalk.gray(` The AI Agent Action Gate v1.0.0\n`));
62
39
  }
63
40
 
64
- async function analyzeCommand(payload, executeAfter, cmd, cmdArgs, isHook) {
65
- const API_KEY = process.env.KAVLR_API_KEY;
66
-
67
- if (API_KEY === undefined) {
68
- console.error('\x1b[31m%s\x1b[0m', 'โŒ KAVLR_API_KEY is missing. Run "kavlr init" or export it manually.');
69
- process.exit(1);
70
- }
71
-
72
- if (isHook === false) {
73
- console.log('\n\x1b[36m%s\x1b[0m', '๐Ÿ” KAVLR analyzing: "' + payload + '"...');
74
- }
75
-
76
- try {
77
- const response = await axios.post(API_URL, {
78
- agent_id: isHook ? 'shell-hook' : 'cli-user',
79
- action_type: cmd || 'run',
80
- payload: payload
81
- }, {
82
- headers: { 'Authorization': 'Bearer ' + API_KEY }
41
+ async function cmdSetup() {
42
+ printLogo();
43
+ const readline = require('readline').createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout
46
+ });
47
+ readline.question(chalk.cyan('Enter your KAVLR API Key (kavlr_...): '), (key) => {
48
+ if (key.startsWith('kavlr_')) {
49
+ saveKey(key.trim());
50
+ } else {
51
+ console.log(chalk.red('Invalid key format. It must start with "kavlr_"'));
52
+ }
53
+ readline.close();
83
54
  });
55
+ }
84
56
 
85
- if (isHook === false) {
86
- console.log('\x1b[32m%s\x1b[0m', 'โœ… [ALLOWED] Action permitted.');
57
+ async function cmdStatus() {
58
+ const key = loadKey();
59
+ if (!key) return console.log(chalk.red('No API key found. Run: kavlr setup'));
60
+ console.log(chalk.gray('Fetching system status...'));
61
+ try {
62
+ const res = await fetch(`${SUPABASE_URL}/rest/v1/settings?id=eq.1&select=kill_switch_active`, {
63
+ headers: { 'apikey': SUPABASE_ANON, 'Authorization': `Bearer ${SUPABASE_ANON}` }
64
+ });
65
+ const data = await res.json();
66
+ const isKilled = data[0] ? data[0].kill_switch_active : false;
67
+
68
+ console.log(chalk.bold('\nSYSTEM STATUS'));
69
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
70
+ console.log(`API Key: ${chalk.green('CONFIGURED')}`);
71
+ console.log(`Kill Switch: ${isKilled ? chalk.red.bold('HALTED (LOCKDOWN)') : chalk.green.bold('SECURE (ACTIVE)')}`);
72
+ console.log(`Gate: ${chalk.cyan('ONLINE')}`);
73
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n');
74
+ } catch (e) {
75
+ console.log(chalk.red(`Error: ${e.message}`));
87
76
  }
88
-
89
- if (executeAfter) {
90
- try {
91
- execSync(cmd + ' ' + cmdArgs, { stdio: 'inherit' });
92
- } catch (err) {
93
- process.exit(err.status || 1);
94
- }
77
+ }
78
+
79
+ async function cmdLogs() {
80
+ const key = loadKey();
81
+ if (!key) return;
82
+ console.log(chalk.gray('Fetching recent telemetry...'));
83
+ try {
84
+ const userRes = await fetch(`${SUPABASE_URL}/rest/v1/api_keys?key=eq.${key}&select=user_id`, {
85
+ headers: { 'apikey': SUPABASE_ANON, 'Authorization': `Bearer ${SUPABASE_ANON}` }
86
+ });
87
+ const users = await userRes.json();
88
+ if (!users.length) return console.log(chalk.red('Invalid API key.'));
89
+
90
+ const logsRes = await fetch(`${SUPABASE_URL}/rest/v1/agent_logs?user_id=eq.${users[0].user_id}&order=created_at.desc&limit=5`, {
91
+ headers: { 'apikey': SUPABASE_ANON, 'Authorization': `Bearer ${SUPABASE_ANON}` }
92
+ });
93
+ const logs = await logsRes.json();
94
+
95
+ console.log(chalk.bold('\nRECENT TELEMETRY'));
96
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€');
97
+ logs.forEach(log => {
98
+ const color = log.status === 'BLOCKED' ? chalk.red : chalk.green;
99
+ console.log(`${color(`[${log.status}]`)} ${log.payload}`);
100
+ console.log(chalk.gray(` ID: ${log.agent_id} | Time: ${log.created_at.substring(11, 19)}`));
101
+ });
102
+ console.log('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n');
103
+ } catch (e) {
104
+ console.log(chalk.red(`Error: ${e.message}`));
95
105
  }
106
+ }
107
+
108
+ function cmdDashboard() {
109
+ const url = "https://kavlr.netlify.app";
110
+ console.log(chalk.green(`Opening ${url}...`));
111
+ const start = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
112
+ execSync(`${start} ${url}`);
113
+ }
114
+
115
+ async function runGate(args) {
116
+ const key = loadKey();
117
+ if (!key) return console.log(chalk.red('No API key found. Run: kavlr setup'));
118
+
119
+ const binary = args[0];
120
+ const cmdArgs = args.slice(1);
96
121
 
97
- } catch (error) {
98
- if (error.response && error.response.status === 403) {
99
- console.error('\x1b[41m\x1b[37m%s\x1b[0m', ' ๐Ÿ›‘ [BLOCKED] THREAT DETECTED ');
100
- console.error('\x1b[31m%s\x1b[0m', 'Reason: ' + error.response.data.message);
101
- } else {
102
- console.error('\x1b[31m%s\x1b[0m', 'โŒ KAVLR API Error. Blocking command for safety.');
122
+ console.log(chalk.gray(`[KAVLR GATE] Parsing action: ${chalk.bold(binary)} with args [${cmdArgs.join(', ')}]`));
123
+
124
+ try {
125
+ const res = await fetch(`${SUPABASE_URL}/rest/v1/rpc/evaluate_kavlr_action_v2`, {
126
+ method: 'POST',
127
+ headers: { 'apikey': SUPABASE_ANON, 'Authorization': `Bearer ${SUPABASE_ANON}`, 'Content-Type': 'application/json' },
128
+ body: JSON.stringify({
129
+ p_api_key: key, p_binary: binary, p_args: cmdArgs, p_cwd: process.cwd()
130
+ })
131
+ });
132
+ const verdict = await res.json();
133
+
134
+ if (verdict.reason === 'NEEDS_AI') {
135
+ console.log(chalk.yellow('[KAVLR AI] Unknown command. Requesting Constitutional AI review...'));
136
+ const aiRes = await fetch(`${SUPABASE_URL}/functions/v1/llm-judge`, {
137
+ method: 'POST',
138
+ headers: { 'Content-Type': 'application/json' },
139
+ body: JSON.stringify({ binary: binary, args: cmdArgs })
140
+ });
141
+ const aiData = await aiRes.json();
142
+
143
+ if (aiData.score >= 90) {
144
+ console.log(chalk.green.bold(`โœ… [ALLOWED] AI Score: ${aiData.score}/100`));
145
+ } else {
146
+ console.log(chalk.red.bold(`๐Ÿ›‘ [BLOCKED] AI Score: ${aiData.score}/100`));
147
+ console.log(chalk.red(` Reason: ${aiData.reason}`));
148
+ process.exit(403);
149
+ }
150
+ } else if (!verdict.allowed) {
151
+ console.log(chalk.red.bold(`๐Ÿ›‘ [BLOCKED] ${verdict.reason}`));
152
+ process.exit(403);
153
+ } else {
154
+ console.log(chalk.green.bold(`โœ… [ALLOWED] ${verdict.reason}`));
155
+ }
156
+
157
+ console.log(chalk.gray('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'));
158
+ const child = spawn(binary, cmdArgs, { stdio: 'inherit' });
159
+ child.on('exit', (code) => {
160
+ console.log(chalk.gray('โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€'));
161
+ process.exit(code);
162
+ });
163
+
164
+ } catch (e) {
165
+ console.log(chalk.red(`[KAVLR ERROR] ${e.message}`));
166
+ process.exit(1);
103
167
  }
104
- process.exit(1);
105
- }
106
168
  }
169
+
170
+ program
171
+ .name('kavlr')
172
+ .description('The AI Agent Action Gate')
173
+ .version('1.0.0')
174
+ .allowExcessArguments(true)
175
+ .allowUnknownOption(true)
176
+ .argument('[args...]', 'Command and arguments')
177
+ .action(async (args) => {
178
+ if (args.length === 0) {
179
+ printLogo();
180
+ console.log(`Usage:\n kavlr setup\n kavlr status\n kavlr logs\n kavlr dashboard\n kavlr <command> [args...]`);
181
+ return;
182
+ }
183
+
184
+ const cmd = args[0];
185
+ if (cmd === 'setup') return cmdSetup();
186
+ if (cmd === 'status') return cmdStatus();
187
+ if (cmd === 'logs') return cmdLogs();
188
+ if (cmd === 'dashboard') return cmdDashboard();
189
+
190
+ await runGate(args);
191
+ });
192
+
193
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,17 +1,13 @@
1
1
  {
2
2
  "name": "@hynaweb/kavlr",
3
- "version": "1.0.0",
4
- "description": "The Agent Leash. Runtime security for AI coding agents.",
3
+ "version": "1.0.1",
4
+ "description": "The AI Agent Action Gate. Grade the action, not the text.",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
7
7
  "kavlr": "bin/cli.js"
8
8
  },
9
- "scripts": {
10
- "start": "node bin/cli.js"
11
- },
12
9
  "dependencies": {
13
- "axios": "^1.6.0"
14
- },
15
- "author": "Harun (HYNAWEB)",
16
- "license": "MIT"
10
+ "chalk": "^4.1.2",
11
+ "commander": "^11.1.0"
12
+ }
17
13
  }