UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

620 lines 26.7 kB
/** * V3 CLI Claims Command * Claims-based authorization, permissions, and access control * * Created with ❤️ by ruv.io */ import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { output } from '../output.js'; const CLAIMS_CONFIG_PATHS = [ '.claude-flow/claims.json', 'claude-flow.claims.json', ]; function getClaimsConfigPaths() { return [ resolve(CLAIMS_CONFIG_PATHS[0]), resolve(CLAIMS_CONFIG_PATHS[1]), resolve(process.env.HOME || '~', '.config/claude-flow/claims.json'), ]; } function loadClaimsConfig() { const configPaths = getClaimsConfigPaths(); for (const configPath of configPaths) { if (existsSync(configPath)) { const content = readFileSync(configPath, 'utf-8'); return { config: JSON.parse(content), path: configPath }; } } // Return default config with the first path as the default write location const defaultConfig = { roles: { admin: ['*'], developer: ['swarm:*', 'agent:*', 'memory:*', 'task:*', 'session:*'], operator: ['swarm:status', 'agent:list', 'memory:read', 'task:list'], viewer: ['*:list', '*:status', '*:read'], }, defaultClaims: ['swarm:create', 'swarm:status', 'agent:spawn', 'agent:list', 'memory:read', 'memory:write', 'task:create'], }; return { config: defaultConfig, path: configPaths[0] }; } function saveClaimsConfig(config, configPath) { const dir = dirname(configPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); } const tmpPath = configPath + '.tmp'; writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf-8'); renameSync(tmpPath, configPath); } // List subcommand const listCommand = { name: 'list', description: 'List claims and permissions', options: [ { name: 'user', short: 'u', type: 'string', description: 'Filter by user ID' }, { name: 'role', short: 'r', type: 'string', description: 'Filter by role' }, { name: 'resource', type: 'string', description: 'Filter by resource' }, ], examples: [ { command: 'claude-flow claims list', description: 'List all claims' }, { command: 'claude-flow claims list -u user123', description: 'List user claims' }, ], action: async (_ctx) => { try { const { config, path: configPath } = loadClaimsConfig(); output.writeln(); output.writeln(output.bold('Claims Configuration')); output.writeln(output.dim('─'.repeat(50))); // Roles table const roles = config.roles || {}; const roleEntries = Object.entries(roles); if (roleEntries.length > 0) { output.writeln(); output.writeln(output.bold('Roles')); output.printTable({ columns: [ { key: 'role', header: 'Role' }, { key: 'count', header: 'Claims' }, { key: 'preview', header: 'Preview', width: 50 }, ], data: roleEntries.map(([name, claims]) => ({ role: name, count: claims.length, preview: claims.slice(0, 4).join(', ') + (claims.length > 4 ? ', ...' : ''), })), border: true, header: true, }); } // Users table const users = config.users || {}; const userEntries = Object.entries(users); if (userEntries.length > 0) { output.writeln(); output.writeln(output.bold('Users')); output.printTable({ columns: [ { key: 'user', header: 'User' }, { key: 'role', header: 'Role' }, { key: 'extraClaims', header: 'Extra Claims' }, ], data: userEntries.map(([name, info]) => ({ user: name, role: info.role || output.dim('(none)'), extraClaims: info.claims ? info.claims.join(', ') : output.dim('(none)'), })), border: true, header: true, }); } // Default claims const defaults = config.defaultClaims || []; if (defaults.length > 0) { output.writeln(); output.writeln(output.bold('Default Claims')); output.printList(defaults); } output.writeln(); output.writeln(output.dim(`Config: ${configPath}`)); return { success: true }; } catch (error) { output.printError(`Failed to list claims: ${error.message}`); return { success: false, exitCode: 1 }; } }, }; // Check subcommand const checkCommand = { name: 'check', description: 'Check if a specific claim is granted', options: [ { name: 'claim', short: 'c', type: 'string', description: 'Claim to check', required: true }, { name: 'user', short: 'u', type: 'string', description: 'User ID to check' }, { name: 'resource', short: 'r', type: 'string', description: 'Resource context' }, ], examples: [ { command: 'claude-flow claims check -c swarm:create', description: 'Check swarm creation permission' }, { command: 'claude-flow claims check -c admin:delete -u user123', description: 'Check user permission' }, ], action: async (ctx) => { const claim = ctx.flags.claim; const user = ctx.flags.user || 'current'; const resource = ctx.flags.resource; if (!claim) { output.printError('Claim is required'); return { success: false, exitCode: 1 }; } output.writeln(); output.writeln(output.bold('Claim Check')); output.writeln(output.dim('─'.repeat(40))); const spinner = output.createSpinner({ text: 'Evaluating claim...', spinner: 'dots' }); spinner.start(); const fs = await import('fs'); const path = await import('path'); // Real claims evaluation from config file let isGranted = false; let reason = 'Claim not found in policy'; let policySource = 'default'; try { // Check for claims config file const claimsConfigPaths = [ path.resolve('.claude-flow/claims.json'), path.resolve('claude-flow.claims.json'), path.resolve(process.env.HOME || '~', '.config/claude-flow/claims.json'), ]; let claimsConfig = { // Default policy - allows basic operations roles: { admin: ['*'], developer: ['swarm:*', 'agent:*', 'memory:*', 'task:*', 'session:*'], operator: ['swarm:status', 'agent:list', 'memory:read', 'task:list'], viewer: ['*:list', '*:status', '*:read'], }, defaultClaims: ['swarm:create', 'swarm:status', 'agent:spawn', 'agent:list', 'memory:read', 'memory:write', 'task:create'], }; for (const configPath of claimsConfigPaths) { if (fs.existsSync(configPath)) { const content = fs.readFileSync(configPath, 'utf-8'); claimsConfig = { ...claimsConfig, ...JSON.parse(content) }; policySource = configPath; break; } } // Resolve user's claims const userConfig = claimsConfig.users?.[user]; let userClaims = [...(claimsConfig.defaultClaims || [])]; if (userConfig) { // Add user-specific claims if (userConfig.claims) { userClaims = [...userClaims, ...userConfig.claims]; } // Add role-based claims if (userConfig.role && claimsConfig.roles?.[userConfig.role]) { userClaims = [...userClaims, ...claimsConfig.roles[userConfig.role]]; } } // Check if claim is granted const checkClaim = (claimToCheck, grantedClaims) => { for (const granted of grantedClaims) { // Exact match if (granted === claimToCheck) return true; // Wildcard match (e.g., "swarm:*" matches "swarm:create") if (granted === '*') return true; if (granted.endsWith(':*')) { const prefix = granted.slice(0, -1); if (claimToCheck.startsWith(prefix)) return true; } // Pattern match (e.g., "*:list" matches "swarm:list") if (granted.startsWith('*:')) { const suffix = granted.slice(1); if (claimToCheck.endsWith(suffix)) return true; } } return false; }; isGranted = checkClaim(claim, userClaims); if (isGranted) { reason = userConfig?.role ? `Granted via role: ${userConfig.role}` : 'Granted via default policy'; } else { reason = 'Not in user claims or role permissions'; } spinner.stop(); } catch (error) { spinner.stop(); // On error, fall back to permissive default isGranted = !claim.startsWith('admin:'); reason = isGranted ? 'Granted (default permissive policy)' : 'Admin claims require explicit grant'; policySource = 'fallback'; } if (isGranted) { output.writeln(output.success('✓ Claim granted')); } else { output.writeln(output.error('✗ Claim denied')); } output.writeln(); output.printBox([ `Claim: ${claim}`, `User: ${user}`, `Resource: ${resource || 'global'}`, `Result: ${isGranted ? output.success('GRANTED') : output.error('DENIED')}`, ``, `Reason: ${reason}`, `Policy: ${policySource}`, ].join('\n'), 'Result'); return { success: isGranted }; }, }; // Grant subcommand const grantCommand = { name: 'grant', description: 'Grant a claim to user or role', options: [ { name: 'claim', short: 'c', type: 'string', description: 'Claim to grant', required: true }, { name: 'user', short: 'u', type: 'string', description: 'User ID' }, { name: 'role', short: 'r', type: 'string', description: 'Role name' }, { name: 'scope', short: 's', type: 'string', description: 'Scope: global, namespace, resource', default: 'global' }, { name: 'expires', short: 'e', type: 'string', description: 'Expiration time (e.g., 24h, 7d)' }, ], examples: [ { command: 'claude-flow claims grant -c swarm:create -u user123', description: 'Grant to user' }, { command: 'claude-flow claims grant -c agent:spawn -r developer', description: 'Grant to role' }, ], action: async (ctx) => { const claim = ctx.flags.claim; const user = ctx.flags.user; const role = ctx.flags.role; if (!claim) { output.printError('Claim is required'); return { success: false, exitCode: 1 }; } if (!user && !role) { output.printError('Either user or role is required'); return { success: false, exitCode: 1 }; } try { const { config, path: configPath } = loadClaimsConfig(); if (user) { if (!config.users) config.users = {}; if (!config.users[user]) config.users[user] = {}; if (!config.users[user].claims) config.users[user].claims = []; if (!config.users[user].claims.includes(claim)) { config.users[user].claims.push(claim); } } if (role) { if (!config.roles) config.roles = {}; if (!config.roles[role]) config.roles[role] = []; if (!config.roles[role].includes(claim)) { config.roles[role].push(claim); } } saveClaimsConfig(config, configPath); output.writeln(); const target = user ? `user "${user}"` : `role "${role}"`; output.writeln(output.success(`Granted "${claim}" to ${target}`)); output.writeln(output.dim(`Saved to: ${configPath}`)); return { success: true }; } catch (error) { output.printError(`Failed to grant claim: ${error.message}`); return { success: false, exitCode: 1 }; } }, }; // Revoke subcommand const revokeCommand = { name: 'revoke', description: 'Revoke a claim from user or role', options: [ { name: 'claim', short: 'c', type: 'string', description: 'Claim to revoke', required: true }, { name: 'user', short: 'u', type: 'string', description: 'User ID' }, { name: 'role', short: 'r', type: 'string', description: 'Role name' }, ], examples: [ { command: 'claude-flow claims revoke -c swarm:delete -u user123', description: 'Revoke from user' }, { command: 'claude-flow claims revoke -c admin:* -r guest', description: 'Revoke from role' }, ], action: async (ctx) => { const claim = ctx.flags.claim; const user = ctx.flags.user; const role = ctx.flags.role; if (!claim) { output.printError('Claim is required'); return { success: false, exitCode: 1 }; } if (!user && !role) { output.printError('Either user or role is required'); return { success: false, exitCode: 1 }; } try { const { config, path: configPath } = loadClaimsConfig(); let removed = false; if (user && config.users?.[user]?.claims) { const idx = config.users[user].claims.indexOf(claim); if (idx !== -1) { config.users[user].claims.splice(idx, 1); removed = true; } } if (role && config.roles?.[role]) { const idx = config.roles[role].indexOf(claim); if (idx !== -1) { config.roles[role].splice(idx, 1); removed = true; } } if (!removed) { const target = user ? `user "${user}"` : `role "${role}"`; output.writeln(); output.printError(`Claim "${claim}" not found on ${target}`); return { success: false, exitCode: 1 }; } saveClaimsConfig(config, configPath); output.writeln(); const target = user ? `user "${user}"` : `role "${role}"`; output.writeln(output.success(`Revoked "${claim}" from ${target}`)); output.writeln(output.dim(`Saved to: ${configPath}`)); return { success: true }; } catch (error) { output.printError(`Failed to revoke claim: ${error.message}`); return { success: false, exitCode: 1 }; } }, }; // Roles subcommand const rolesCommand = { name: 'roles', description: 'Manage roles and their claims', options: [ { name: 'action', short: 'a', type: 'string', description: 'Action: list, create, delete, show', default: 'list' }, { name: 'name', short: 'n', type: 'string', description: 'Role name' }, ], examples: [ { command: 'claude-flow claims roles', description: 'List all roles' }, { command: 'claude-flow claims roles -a show -n admin', description: 'Show role details' }, ], action: async (ctx) => { const action = ctx.flags.action || 'list'; const name = ctx.flags.name; try { const { config, path: configPath } = loadClaimsConfig(); if (action === 'list') { const roles = config.roles || {}; const entries = Object.entries(roles); if (entries.length === 0) { output.writeln(); output.writeln(output.dim('No roles defined.')); return { success: true }; } output.writeln(); output.writeln(output.bold('Roles')); output.printTable({ columns: [ { key: 'role', header: 'Role' }, { key: 'count', header: 'Claims' }, { key: 'claims', header: 'Claims List', width: 60 }, ], data: entries.map(([roleName, claims]) => ({ role: roleName, count: claims.length, claims: claims.join(', '), })), border: true, header: true, }); output.writeln(output.dim(`Config: ${configPath}`)); return { success: true }; } if (action === 'show') { if (!name) { output.printError('Role name is required (use -n <name>)'); return { success: false, exitCode: 1 }; } const claims = config.roles?.[name]; if (!claims) { output.printError(`Role "${name}" not found`); return { success: false, exitCode: 1 }; } output.writeln(); output.writeln(output.bold(`Role: ${name}`)); output.writeln(output.dim('─'.repeat(40))); output.writeln(`Claims (${claims.length}):`); output.printList(claims); return { success: true }; } if (action === 'create') { if (!name) { output.printError('Role name is required (use -n <name>)'); return { success: false, exitCode: 1 }; } if (!config.roles) config.roles = {}; if (config.roles[name]) { output.printError(`Role "${name}" already exists`); return { success: false, exitCode: 1 }; } config.roles[name] = []; saveClaimsConfig(config, configPath); output.writeln(); output.writeln(output.success(`Created role "${name}"`)); output.writeln(output.dim('Use "claims grant -c <claim> -r ' + name + '" to add claims.')); return { success: true }; } if (action === 'delete') { if (!name) { output.printError('Role name is required (use -n <name>)'); return { success: false, exitCode: 1 }; } if (!config.roles?.[name]) { output.printError(`Role "${name}" not found`); return { success: false, exitCode: 1 }; } delete config.roles[name]; saveClaimsConfig(config, configPath); output.writeln(); output.writeln(output.success(`Deleted role "${name}"`)); return { success: true }; } output.printError(`Unknown action "${action}". Use: list, create, delete, show`); return { success: false, exitCode: 1 }; } catch (error) { output.printError(`Failed to manage roles: ${error.message}`); return { success: false, exitCode: 1 }; } }, }; // Policies subcommand const policiesCommand = { name: 'policies', description: 'Manage claim policies', options: [ { name: 'action', short: 'a', type: 'string', description: 'Action: list, create, delete', default: 'list' }, { name: 'name', short: 'n', type: 'string', description: 'Policy name' }, ], examples: [ { command: 'claude-flow claims policies', description: 'List policies' }, { command: 'claude-flow claims policies -a create -n rate-limit', description: 'Create policy' }, ], action: async (ctx) => { const action = ctx.flags.action || 'list'; const name = ctx.flags.name; try { const { config, path: configPath } = loadClaimsConfig(); if (action === 'list') { output.writeln(); output.writeln(output.bold('Policies')); output.writeln(output.dim('─'.repeat(50))); // Default claims as a policy const defaults = config.defaultClaims || []; output.writeln(); output.writeln(output.bold('Default Policy')); if (defaults.length > 0) { output.printList(defaults); } else { output.writeln(output.dim(' (no default claims)')); } // Role-based policies const roles = config.roles || {}; const entries = Object.entries(roles); if (entries.length > 0) { output.writeln(); output.writeln(output.bold('Role-Based Policies')); output.printTable({ columns: [ { key: 'policy', header: 'Policy (Role)' }, { key: 'count', header: 'Claims' }, { key: 'preview', header: 'Preview', width: 50 }, ], data: entries.map(([roleName, claims]) => ({ policy: roleName, count: claims.length, preview: claims.slice(0, 4).join(', ') + (claims.length > 4 ? ', ...' : ''), })), border: true, header: true, }); } output.writeln(); output.writeln(output.dim(`Config: ${configPath}`)); return { success: true }; } if (action === 'create') { if (!name) { output.printError('Policy name is required (use -n <name>)'); return { success: false, exitCode: 1 }; } if (!config.roles) config.roles = {}; if (config.roles[name]) { output.printError(`Policy "${name}" already exists`); return { success: false, exitCode: 1 }; } config.roles[name] = []; saveClaimsConfig(config, configPath); output.writeln(); output.writeln(output.success(`Created policy "${name}"`)); output.writeln(output.dim('Use "claims grant -c <claim> -r ' + name + '" to add claims.')); return { success: true }; } if (action === 'delete') { if (!name) { output.printError('Policy name is required (use -n <name>)'); return { success: false, exitCode: 1 }; } if (!config.roles?.[name]) { output.printError(`Policy "${name}" not found`); return { success: false, exitCode: 1 }; } delete config.roles[name]; saveClaimsConfig(config, configPath); output.writeln(); output.writeln(output.success(`Deleted policy "${name}"`)); return { success: true }; } output.printError(`Unknown action "${action}". Use: list, create, delete`); return { success: false, exitCode: 1 }; } catch (error) { output.printError(`Failed to manage policies: ${error.message}`); return { success: false, exitCode: 1 }; } }, }; // Main claims command export const claimsCommand = { name: 'claims', description: 'Claims-based authorization, permissions, and access control', subcommands: [listCommand, checkCommand, grantCommand, revokeCommand, rolesCommand, policiesCommand], examples: [ { command: 'claude-flow claims list', description: 'List all claims' }, { command: 'claude-flow claims check -c swarm:create', description: 'Check permission' }, { command: 'claude-flow claims grant -c agent:spawn -r developer', description: 'Grant claim' }, ], action: async () => { output.writeln(); output.writeln(output.bold('RuFlo Claims System')); output.writeln(output.dim('Fine-grained authorization and access control')); output.writeln(); output.writeln('Subcommands:'); output.printList([ 'list - List claims and permissions', 'check - Check if a claim is granted', 'grant - Grant a claim to user or role', 'revoke - Revoke a claim', 'roles - Manage roles and their claims', 'policies - Manage claim policies', ]); output.writeln(); output.writeln('Claim Types:'); output.printList([ 'swarm:* - Swarm operations (create, delete, scale)', 'agent:* - Agent operations (spawn, terminate)', 'memory:* - Memory operations (read, write, delete)', 'admin:* - Administrative operations', ]); output.writeln(); output.writeln(output.dim('Created with ❤️ by ruv.io')); return { success: true }; }, }; export default claimsCommand; //# sourceMappingURL=claims.js.map