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
JavaScript
/**
* 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