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
204 lines (186 loc) • 6.3 kB
text/typescript
/**
* Standalone CLI shim for @claude-flow/plugin-agent-federation.
* Maps `ruflo-federation <subcommand> [args]` -> CLICommandDefinition handlers.
*
* Auth/config via .env (loaded from CWD or any parent):
* FEDERATION_NODE_NAME - this node's identity (default: hostname)
* FEDERATION_BIND_HOST - bind address (default: 0.0.0.0)
* FEDERATION_BIND_PORT - bind port (default: 8443)
* FEDERATION_TRUST_LEVEL - default trust tier (default: untrusted)
*/
import { readFileSync, existsSync } from 'node:fs';
import { resolve } from 'node:path';
import { hostname } from 'node:os';
import { AgentFederationPlugin } from './plugin.js';
import type { PluginContext } from '@claude-flow/shared/src/plugin-interface.js';
function loadDotenv(): { loaded: string | null; count: number } {
let dir = process.cwd();
for (let i = 0; i < 8; i++) {
const candidate = resolve(dir, '.env');
if (existsSync(candidate)) {
const text = readFileSync(candidate, 'utf8');
let count = 0;
for (const rawLine of text.split('\n')) {
const line = rawLine.trim();
if (!line || line.startsWith('#')) continue;
const eq = line.indexOf('=');
if (eq < 1) continue;
const key = line.slice(0, eq).trim();
let val = line.slice(eq + 1).trim();
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
val = val.slice(1, -1);
}
if (process.env[key] === undefined) {
process.env[key] = val;
count++;
}
}
return { loaded: candidate, count };
}
const parent = resolve(dir, '..');
if (parent === dir) break;
dir = parent;
}
return { loaded: null, count: 0 };
}
loadDotenv();
interface ParsedArgs {
_: string[];
[k: string]: unknown;
}
function parseArgs(argv: string[]): ParsedArgs {
const out: ParsedArgs = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i]!;
if (a.startsWith('--')) {
const eq = a.indexOf('=');
if (eq > -1) {
out[a.slice(2, eq)] = a.slice(eq + 1);
} else {
const key = a.slice(2);
const next = argv[i + 1];
if (next === undefined || next.startsWith('-')) {
out[key] = true;
} else {
out[key] = next;
i++;
}
}
} else if (a.startsWith('-') && a.length > 1) {
const key = a.slice(1);
const next = argv[i + 1];
if (next === undefined || next.startsWith('-')) {
out[key] = true;
} else {
out[key] = next;
i++;
}
} else {
out._.push(a);
}
}
return out;
}
function aliasShortToLong(args: ParsedArgs): ParsedArgs {
const aliases: Record<string, string> = {
n: 'name',
h: 'host',
p: 'port',
t: 'token',
u: 'url',
f: 'format',
};
for (const [s, l] of Object.entries(aliases)) {
if (s in args && !(l in args)) {
args[l] = args[s];
}
}
return args;
}
/**
* Resolve federation subcommand. Supports multi-word like
* `federation peers add`, `federation peers remove`.
*/
function resolveCommand(
argv: string[],
commandNames: string[],
): { name: string; rest: string[] } | null {
const tokens = argv.slice();
for (const len of [3, 2, 1]) {
if (tokens.length < len) continue;
const probe = 'federation ' + tokens.slice(0, len).join(' ');
if (commandNames.includes(probe)) {
return { name: probe, rest: tokens.slice(len) };
}
}
return null;
}
function makeContext(): PluginContext {
const noop = () => undefined;
return {
config: {
nodeName: process.env.FEDERATION_NODE_NAME ?? hostname(),
bindHost: process.env.FEDERATION_BIND_HOST ?? '0.0.0.0',
bindPort: Number(process.env.FEDERATION_BIND_PORT ?? 8443),
defaultTrustLevel: process.env.FEDERATION_TRUST_LEVEL ?? 'untrusted',
},
eventBus: { emit: noop, on: noop, off: noop, once: noop } as unknown as PluginContext['eventBus'],
logger: {
info: () => {},
warn: (...a: unknown[]) => console.warn('[warn]', ...a),
error: (...a: unknown[]) => console.error('[error]', ...a),
debug: (...a: unknown[]) => process.env.DEBUG && console.error('[debug]', ...a),
} as unknown as PluginContext['logger'],
services: { get: () => undefined, register: noop, has: () => false } as unknown as PluginContext['services'],
};
}
function printHelp(commandNames: string[]): void {
console.log('ruflo-federation — Cross-installation agent federation CLI');
console.log('');
console.log('Usage: ruflo-federation <subcommand> [options]');
console.log('');
console.log('Config (via .env or env vars):');
console.log(' FEDERATION_NODE_NAME, FEDERATION_BIND_HOST, FEDERATION_BIND_PORT, FEDERATION_TRUST_LEVEL');
console.log('');
console.log('Subcommands:');
for (const n of commandNames) {
console.log(' ' + n.replace(/^federation /, ''));
}
console.log('');
console.log('Run "ruflo-federation <subcommand> --help" for command-specific options.');
}
async function main(): Promise<void> {
const argv = process.argv.slice(2);
const plugin = new AgentFederationPlugin();
const context = makeContext();
await plugin.initialize(context);
const commands = plugin.registerCLICommands();
const commandNames = commands.map((c) => c.name);
if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
printHelp(commandNames);
return;
}
const resolved = resolveCommand(argv, commandNames);
if (!resolved) {
console.error('Unknown subcommand: ' + argv.join(' '));
console.error('Run "ruflo-federation --help" to list available subcommands.');
process.exitCode = 1;
return;
}
const cmd = commands.find((c) => c.name === resolved.name)!;
const args = aliasShortToLong(parseArgs(resolved.rest));
try {
await cmd.handler(args as never);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.error('Error: ' + msg);
process.exitCode = 1;
}
}
main()
.then(() => process.exit(process.exitCode ?? 0))
.catch((err) => {
console.error('Fatal: ' + (err instanceof Error ? err.message : String(err)));
process.exit(1);
});