UNPKG

@webdevtoday/grok-cli

Version:

A sophisticated CLI tool for interacting with xAI Grok 4, featuring conversation history, file reference, custom commands, memory system, and genetic development workflows

727 lines 29.1 kB
"use strict"; /** * Enhanced Permission System for Grok CLI * Provides interactive prompts, persistence, and fine-grained control */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnhancedPermissionManager = void 0; const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); /** * Enhanced Permission Manager with interactive prompts and persistence */ class EnhancedPermissionManager { constructor(config, cwd = process.cwd()) { this.sessionDecisions = new Map(); this.persistentRules = []; this.auditLog = []; this.config = config; this.cwd = cwd; this.loadPersistentRules(); } /** * Check if a tool has permission to execute */ checkToolPermission(toolName, params) { const permissions = this.config.permissions; // Check disallowed tools first if (permissions.disallowedTools?.includes(toolName)) { this.auditDecision(toolName, params, 'deny', 'Tool in disallowed list'); return false; } // Check persistent rules const rule = this.findMatchingRule(toolName, params); if (rule) { const allowed = rule.decision === 'allow'; this.auditDecision(toolName, params, rule.decision, rule.reason || 'Persistent rule'); return allowed; } // Check session decisions const sessionKey = this.generateSessionKey(toolName, params); const sessionDecision = this.sessionDecisions.get(sessionKey); if (sessionDecision) { const allowed = sessionDecision.decision === 'allow' || sessionDecision.decision === 'always'; this.auditDecision(toolName, params, allowed ? 'allow' : 'deny', 'Session decision'); return allowed; } // Check allowed tools (if specified) if (permissions.allowedTools && permissions.allowedTools.length > 0) { const allowed = permissions.allowedTools.includes(toolName); this.auditDecision(toolName, params, allowed ? 'allow' : 'deny', 'Tool allowlist'); return allowed; } // Default to allowed if no restrictions return true; } /** * Request permission with interactive prompt */ async requestPermission(toolName, params) { const permissions = this.config.permissions; switch (permissions.mode) { case 'auto': this.auditDecision(toolName, params, 'allow', 'Auto-approve mode'); return true; case 'full': this.auditDecision(toolName, params, 'allow', 'Full access mode'); return true; case 'plan': await this.showPlannedExecution(toolName, params); this.auditDecision(toolName, params, 'deny', 'Plan mode - execution blocked'); return false; case 'ask': default: return await this.askUserPermission(toolName, params); } } /** * Show interactive permission prompt */ async askUserPermission(toolName, params) { const readline = await Promise.resolve().then(() => __importStar(require('readline'))); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); console.log(); console.log(chalk_1.default.yellow('🔐 Permission Required')); console.log(chalk_1.default.cyan('═══════════════════════')); console.log(); console.log(chalk_1.default.blue('Tool:'), chalk_1.default.bold(toolName)); // Show tool description if available const toolDescription = this.getToolDescription(toolName); if (toolDescription) { console.log(chalk_1.default.blue('Description:'), toolDescription); } // Show parameters with security assessment console.log(chalk_1.default.blue('Parameters:')); this.displayParameters(params); // Show security assessment const riskLevel = this.assessRisk(toolName, params); console.log(); console.log(chalk_1.default.blue('Risk Level:'), this.formatRiskLevel(riskLevel)); // Show similar previous decisions const similarDecisions = this.findSimilarDecisions(toolName, params); if (similarDecisions.length > 0) { console.log(); console.log(chalk_1.default.gray('Previous similar decisions:')); similarDecisions.slice(0, 3).forEach(decision => { const timeAgo = this.getTimeAgo(decision.timestamp); console.log(` ${decision.decision === 'allow' ? chalk_1.default.green('✓') : chalk_1.default.red('✗')} ${timeAgo} - ${decision.reason || 'No reason'}`); }); } console.log(); console.log(chalk_1.default.gray('Options:')); console.log(chalk_1.default.green(' [a] Allow once')); console.log(chalk_1.default.green(' [A] Allow always for this tool')); console.log(chalk_1.default.red(' [d] Deny once')); console.log(chalk_1.default.red(' [D] Deny always for this tool')); console.log(chalk_1.default.blue(' [i] Show more information')); console.log(chalk_1.default.gray(' [q] Quit')); console.log(); return new Promise((resolve) => { const askChoice = () => { rl.question(chalk_1.default.cyan('Choose [a/A/d/D/i/q]: '), async (answer) => { const choice = answer.toLowerCase().trim(); switch (choice) { case 'a': this.recordSessionDecision(toolName, params, 'allow', 'User allowed once'); this.auditDecision(toolName, params, 'allow', 'User allowed once'); rl.close(); resolve(true); break; case 'allow': this.recordSessionDecision(toolName, params, 'allow', 'User allowed once'); this.auditDecision(toolName, params, 'allow', 'User allowed once'); rl.close(); resolve(true); break; case 'A': await this.createPersistentRule(toolName, params, 'allow', 'User allowed always'); this.auditDecision(toolName, params, 'allow', 'User allowed always'); rl.close(); resolve(true); break; case 'd': this.recordSessionDecision(toolName, params, 'deny', 'User denied once'); this.auditDecision(toolName, params, 'deny', 'User denied once'); rl.close(); resolve(false); break; case 'deny': this.recordSessionDecision(toolName, params, 'deny', 'User denied once'); this.auditDecision(toolName, params, 'deny', 'User denied once'); rl.close(); resolve(false); break; case 'D': await this.createPersistentRule(toolName, params, 'deny', 'User denied always'); this.auditDecision(toolName, params, 'deny', 'User denied always'); rl.close(); resolve(false); break; case 'i': await this.showDetailedInformation(toolName, params); askChoice(); // Ask again after showing info break; case 'q': case 'quit': console.log(chalk_1.default.gray('Operation cancelled')); this.auditDecision(toolName, params, 'deny', 'User cancelled'); rl.close(); resolve(false); break; default: console.log(chalk_1.default.red('Invalid choice. Please enter a, A, d, D, i, or q')); askChoice(); break; } }); }; askChoice(); }); } /** * Show planned execution without executing */ async showPlannedExecution(toolName, params) { console.log(); console.log(chalk_1.default.blue('🔍 Planning Mode - Would Execute:')); console.log(chalk_1.default.cyan('════════════════════════════════')); console.log(); console.log(chalk_1.default.blue('Tool:'), chalk_1.default.bold(toolName)); const toolDescription = this.getToolDescription(toolName); if (toolDescription) { console.log(chalk_1.default.blue('Description:'), toolDescription); } console.log(chalk_1.default.blue('Parameters:')); this.displayParameters(params); const riskLevel = this.assessRisk(toolName, params); console.log(); console.log(chalk_1.default.blue('Risk Level:'), this.formatRiskLevel(riskLevel)); // Show what the tool would likely do const prediction = this.predictToolBehavior(toolName, params); if (prediction) { console.log(); console.log(chalk_1.default.blue('Predicted Actions:')); console.log(chalk_1.default.gray(` ${prediction}`)); } console.log(); console.log(chalk_1.default.yellow('⚠️ Execution blocked in planning mode')); } /** * Display parameters with formatting and security assessment */ displayParameters(params) { if (Object.keys(params).length === 0) { console.log(chalk_1.default.gray(' (no parameters)')); return; } for (const [key, value] of Object.entries(params)) { const sensitivity = this.assessParameterSensitivity(key, value); const formattedValue = this.formatParameterValue(value, sensitivity); console.log(` ${chalk_1.default.cyan(key)}: ${formattedValue}`); if (sensitivity === 'high') { console.log(` ${chalk_1.default.red('⚠️ Sensitive parameter')}`); } } } /** * Show detailed information about the tool and operation */ async showDetailedInformation(toolName, params) { console.log(); console.log(chalk_1.default.blue('📋 Detailed Information')); console.log(chalk_1.default.cyan('═══════════════════════')); console.log(); // Tool information console.log(chalk_1.default.blue('Tool Details:')); console.log(` Name: ${chalk_1.default.bold(toolName)}`); const description = this.getToolDescription(toolName); if (description) { console.log(` Description: ${description}`); } // Parameter analysis console.log(); console.log(chalk_1.default.blue('Parameter Analysis:')); for (const [key, value] of Object.entries(params)) { const sensitivity = this.assessParameterSensitivity(key, value); const analysis = this.analyzeParameter(key, value); console.log(` ${chalk_1.default.cyan(key)}:`); console.log(` Value: ${this.formatParameterValue(value, sensitivity)}`); console.log(` Sensitivity: ${this.formatSensitivity(sensitivity)}`); if (analysis) { console.log(` Analysis: ${chalk_1.default.gray(analysis)}`); } } // Security assessment console.log(); console.log(chalk_1.default.blue('Security Assessment:')); const riskLevel = this.assessRisk(toolName, params); const riskReasons = this.getRiskReasons(toolName, params); console.log(` Risk Level: ${this.formatRiskLevel(riskLevel)}`); if (riskReasons.length > 0) { console.log(` Risk Factors:`); riskReasons.forEach(reason => { console.log(` • ${chalk_1.default.gray(reason)}`); }); } // Tool capabilities const capabilities = this.getToolCapabilities(toolName); if (capabilities.length > 0) { console.log(); console.log(chalk_1.default.blue('Tool Capabilities:')); capabilities.forEach(capability => { console.log(` • ${chalk_1.default.gray(capability)}`); }); } console.log(); console.log(chalk_1.default.gray('Press Enter to continue...')); const readline = await Promise.resolve().then(() => __importStar(require('readline'))); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { rl.question('', () => { rl.close(); resolve(); }); }); } /** * Record session-level permission decision */ recordSessionDecision(toolName, params, decision, reason) { const sessionKey = this.generateSessionKey(toolName, params); this.sessionDecisions.set(sessionKey, { toolName, decision, timestamp: new Date(), params, reason }); } /** * Create persistent permission rule */ async createPersistentRule(toolName, _params, decision, reason) { const rule = { toolPattern: toolName, decision, scope: 'project', // Default to project scope reason, // No expiration by default }; this.persistentRules.push(rule); await this.savePersistentRules(); console.log(chalk_1.default.green(`✅ Created persistent rule: ${decision} ${toolName}`)); } /** * Load persistent rules from disk */ async loadPersistentRules() { try { const rulesPath = path_1.default.join(this.cwd, '.grok', 'permissions.json'); const rulesContent = await fs_1.promises.readFile(rulesPath, 'utf-8'); const rulesData = JSON.parse(rulesContent); this.persistentRules = rulesData.rules || []; // Clean up expired rules const now = new Date(); this.persistentRules = this.persistentRules.filter(rule => !rule.expires || rule.expires > now); } catch { // No persistent rules file exists yet, that's fine } } /** * Save persistent rules to disk */ async savePersistentRules() { try { const rulesDir = path_1.default.join(this.cwd, '.grok'); await fs_1.promises.mkdir(rulesDir, { recursive: true }); const rulesPath = path_1.default.join(rulesDir, 'permissions.json'); const rulesData = { version: '1.0', rules: this.persistentRules, lastUpdated: new Date().toISOString() }; await fs_1.promises.writeFile(rulesPath, JSON.stringify(rulesData, null, 2), 'utf-8'); } catch (error) { console.warn(chalk_1.default.yellow('⚠️ Failed to save permission rules:'), error instanceof Error ? error.message : String(error)); } } /** * Find matching persistent rule */ findMatchingRule(toolName, params) { return this.persistentRules.find(rule => { // Check tool pattern match if (rule.toolPattern !== toolName && !this.matchesPattern(toolName, rule.toolPattern)) { return false; } // Check parameter patterns if specified if (rule.paramPatterns) { for (const [paramKey, pattern] of Object.entries(rule.paramPatterns)) { const paramValue = params[paramKey]; if (!paramValue || !this.matchesPattern(String(paramValue), pattern)) { return false; } } } // Check if rule is expired if (rule.expires && rule.expires <= new Date()) { return false; } return true; }) || null; } /** * Check if string matches a pattern (supports basic wildcards) */ matchesPattern(text, pattern) { const regexPattern = pattern .replace(/\*/g, '.*') .replace(/\?/g, '.'); const regex = new RegExp(`^${regexPattern}$`, 'i'); return regex.test(text); } /** * Generate session key for caching decisions */ generateSessionKey(toolName, params) { const paramStr = JSON.stringify(params, Object.keys(params).sort()); return `${toolName}:${paramStr}`; } /** * Assess risk level of tool execution */ assessRisk(toolName, params) { let riskScore = 0; // Tool-based risk assessment const highRiskTools = ['bash', 'tmux', 'custom']; const mediumRiskTools = ['write', 'read']; if (highRiskTools.includes(toolName.toLowerCase())) { riskScore += 3; } else if (mediumRiskTools.includes(toolName.toLowerCase())) { riskScore += 1; } // Parameter-based risk assessment for (const [key, value] of Object.entries(params)) { const sensitivity = this.assessParameterSensitivity(key, value); if (sensitivity === 'high') { riskScore += 2; } else if (sensitivity === 'medium') { riskScore += 1; } } if (riskScore >= 4) return 'high'; if (riskScore >= 2) return 'medium'; return 'low'; } /** * Get reasons for risk assessment */ getRiskReasons(toolName, params) { const reasons = []; // Tool-specific risks switch (toolName.toLowerCase()) { case 'bash': reasons.push('Can execute arbitrary system commands'); break; case 'write': reasons.push('Can modify files on disk'); break; case 'tmux': reasons.push('Can create and manage terminal sessions'); break; case 'custom': reasons.push('Executes user-defined commands'); break; } // Parameter-specific risks for (const [key, value] of Object.entries(params)) { if (key.includes('path') && typeof value === 'string') { if (value.includes('..')) { reasons.push('Path traversal detected'); } if (value.startsWith('/')) { reasons.push('Absolute path usage'); } } if (key.includes('command') && typeof value === 'string') { if (value.includes('rm ') || value.includes('del ')) { reasons.push('Potentially destructive command'); } if (value.includes('sudo') || value.includes('su ')) { reasons.push('Privilege escalation attempt'); } } } return reasons; } /** * Assess parameter sensitivity */ assessParameterSensitivity(key, value) { const keyLower = key.toLowerCase(); // High sensitivity keywords if (keyLower.includes('password') || keyLower.includes('secret') || keyLower.includes('token') || keyLower.includes('key') || keyLower.includes('credential')) { return 'high'; } // Medium sensitivity if (keyLower.includes('path') || keyLower.includes('command') || keyLower.includes('script') || keyLower.includes('exec')) { return 'medium'; } // Check value patterns if (typeof value === 'string') { if (value.includes('/') && value.length > 10) return 'medium'; if (value.includes('..')) return 'high'; if (value.match(/^[A-Za-z0-9+/]{20,}={0,2}$/)) return 'high'; // Base64-like } return 'low'; } /** * Format parameter value for display */ formatParameterValue(value, sensitivity) { if (sensitivity === 'high') { return chalk_1.default.red('[REDACTED]'); } const valueStr = typeof value === 'string' ? value : JSON.stringify(value); if (valueStr.length > 100) { return chalk_1.default.gray(valueStr.substring(0, 100) + '...'); } return chalk_1.default.white(valueStr); } /** * Format sensitivity level */ formatSensitivity(sensitivity) { switch (sensitivity) { case 'high': return chalk_1.default.red('High'); case 'medium': return chalk_1.default.yellow('Medium'); case 'low': return chalk_1.default.green('Low'); } } /** * Format risk level with colors */ formatRiskLevel(risk) { switch (risk) { case 'high': return chalk_1.default.red.bold('🔴 HIGH'); case 'medium': return chalk_1.default.yellow.bold('🟡 MEDIUM'); case 'low': return chalk_1.default.green.bold('🟢 LOW'); } } /** * Get tool description */ getToolDescription(toolName) { const descriptions = { 'bash': 'Execute shell commands on the system', 'read': 'Read files from the filesystem', 'write': 'Write or modify files on the filesystem', 'tmux': 'Create and manage terminal sessions', 'custom': 'Execute user-defined custom commands', }; return descriptions[toolName.toLowerCase()] || null; } /** * Get tool capabilities */ getToolCapabilities(toolName) { const capabilities = { 'bash': [ 'Execute any shell command', 'Access system utilities', 'Modify system state', 'Access environment variables' ], 'read': [ 'Read file contents', 'Access file metadata', 'Traverse directory structure' ], 'write': [ 'Create new files', 'Modify existing files', 'Create directories', 'Change file permissions' ], 'tmux': [ 'Create terminal sessions', 'Run background processes', 'Split terminal windows', 'Manage session state' ] }; return capabilities[toolName.toLowerCase()] || []; } /** * Predict tool behavior */ predictToolBehavior(toolName, params) { switch (toolName.toLowerCase()) { case 'bash': const command = params['command']; return command ? `Execute command: ${command}` : 'Execute shell command'; case 'read': const readPath = params['file_path']; return readPath ? `Read file: ${readPath}` : 'Read file'; case 'write': const writePath = params['file_path']; return writePath ? `Write to file: ${writePath}` : 'Write file'; default: return null; } } /** * Analyze parameter */ analyzeParameter(key, value) { if (key.includes('path') && typeof value === 'string') { if (path_1.default.isAbsolute(value)) { return 'Absolute path - has system-wide access'; } if (value.includes('..')) { return 'Contains parent directory references'; } } if (key.includes('command') && typeof value === 'string') { const dangerousPatterns = ['rm ', 'del ', 'sudo ', 'su ', '>', '>>', '|']; const found = dangerousPatterns.find(pattern => value.includes(pattern)); if (found) { return `Contains potentially dangerous pattern: ${found}`; } } return null; } /** * Find similar previous decisions */ findSimilarDecisions(toolName, _params) { const similar = []; // Check session decisions for (const decision of this.sessionDecisions.values()) { if (decision.toolName === toolName) { similar.push(decision); } } return similar.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } /** * Record audit entry */ auditDecision(toolName, params, decision, reason) { const entry = { timestamp: new Date(), toolName, params, decision, mode: this.config.permissions.mode, reason, user: process.env['USER'] || process.env['USERNAME'] || 'unknown', sessionId: 'current' // TODO: Get actual session ID }; this.auditLog.push(entry); // Keep only last 1000 entries if (this.auditLog.length > 1000) { this.auditLog = this.auditLog.slice(-1000); } } /** * Get audit log */ getAuditLog() { return [...this.auditLog]; } /** * Get permission statistics */ getPermissionStats() { const allowed = this.auditLog.filter(entry => entry.decision === 'allow').length; const denied = this.auditLog.filter(entry => entry.decision === 'deny').length; return { totalDecisions: this.auditLog.length, allowedCount: allowed, deniedCount: denied, ruleCount: this.persistentRules.length, sessionDecisions: this.sessionDecisions.size }; } /** * Clear session decisions */ clearSessionDecisions() { this.sessionDecisions.clear(); } /** * Get time ago string */ getTimeAgo(date) { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays > 0) return `${diffDays}d ago`; if (diffHours > 0) return `${diffHours}h ago`; if (diffMinutes > 0) return `${diffMinutes}m ago`; return 'just now'; } } exports.EnhancedPermissionManager = EnhancedPermissionManager; //# sourceMappingURL=permissions.js.map