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

605 lines (551 loc) 24.7 kB
/** * Statusline Configuration Generator (Optimized) * Creates fast, reliable statusline for V3 progress display * * Performance: * - Single combined git execSync call (not 8+ separate ones) * - process.memoryUsage() instead of ps aux * - No recursive test file content reading * - Shared settings cache * - Strict 2s timeouts on all shell calls */ /** * Generate optimized statusline script * Output format: * ▊ RuFlo V3.6 ● user │ ⎇ branch │ Opus 4.7 * ───────────────────────────────────────────────────── * 🏗️ DDD Domains [●●○○○] 2/5 ⚡ HNSW 150x * 🤖 Swarm ◉ [ 5/15] 👥 2 🪝 10/17 🟢 CVE 3/3 💾 4MB 🧠 63% * 🔧 Architecture ADRs ●71% │ DDD ● 13% │ Security ●CLEAN * 📊 AgentDB Vectors ●3104⚡ │ Size 216KB │ Tests ●6 (~24 cases) │ MCP ●1/1 */ export function generateStatuslineScript(options) { const maxAgents = options.runtime.maxAgents; return `#!/usr/bin/env node /** * RuFlo V3 Statusline — delegation build (#2195) * * Fix for ruvnet/ruflo#2195: the previous version re-implemented all data * readers locally using fragile file probes that missed AgentDB patterns, * the v3/docs/adr/ ADR directory, and the real vector count. * * This version delegates to 'npx @claude-flow/cli hooks statusline --json' * as the single source of truth. That command queries AgentDB directly, * counts ADRs in both directories, and reports the real intelligence pct. * * ADR counting falls back to local file reads so the display still works * without network access (counts both v3/docs/adr/ and v3/implementation/adrs/). * * Cache: JSON result is cached in /tmp for 10s so rapid prompt triggers * (every keystroke in some shells) don't hammer the CLI on every call. * * Usage: node statusline.cjs [--json] [--compact] [--dashboard] */ /* eslint-disable @typescript-eslint/no-var-requires */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const os = require('os'); // Configuration const CONFIG = { maxAgents: ${maxAgents}, // Session-cost display. Claude Code's cost.total_cost_usd is a client-side // estimate that "may differ from your actual bill" and reads as misleading on // subscription plans, where token usage is not billed per dollar. These let // each user pick what the segment means to them without changing the default. // RUFLO_STATUSLINE_COST_SYMBOL override the leading '$' (e.g. ⚡, €, 🌱); // set to an empty string for the number alone. // RUFLO_STATUSLINE_HIDE_COST 1/true/yes/on removes the segment entirely. costSymbol: process.env.RUFLO_STATUSLINE_COST_SYMBOL ?? '$', hideCost: /^(1|true|yes|on)$/i.test(process.env.RUFLO_STATUSLINE_HIDE_COST || ''), }; const CWD = process.cwd(); // ─── Delegation cache ─────────────────────────────────────────── // Cache the CLI JSON result for 10s so rapid prompt re-renders // (e.g. every keypress in some shells) don't re-invoke npx each time. const CACHE_FILE = path.join(os.tmpdir(), 'ruflo-statusline-cache-' + require('crypto').createHash('md5').update(CWD).digest('hex').slice(0, 8) + '.json'); const CACHE_TTL_MS = 10000; function readCache() { try { if (fs.existsSync(CACHE_FILE)) { const raw = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8')); if (raw && raw._ts && (Date.now() - raw._ts) < CACHE_TTL_MS) { return raw.data; } } } catch { /* ignore */ } return null; } function writeCache(data) { try { fs.writeFileSync(CACHE_FILE, JSON.stringify({ _ts: Date.now(), data }), 'utf-8'); } catch { /* ignore */ } } /** * Single source of truth: delegate to the CLI hooks statusline --json command. * Falls back to a minimal static object on failure so the statusline still renders. * * Fix for ruflo#2195: the previous local readers returned 0 for AgentDB patterns * (missed the .swarm/memory.db → AgentDB path), computed dddProgress wrong, * and only counted ADRs in v3/implementation/adrs/ (missed v3/docs/adr/). */ function getStatuslineData() { const cached = readCache(); if (cached) return cached; try { const raw = execSync( 'npx --yes @claude-flow/cli@latest hooks statusline --json 2>/dev/null', { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], cwd: CWD } ).trim(); // The CLI may emit preamble lines before the JSON — find the first '{'. const jsonStart = raw.indexOf('{'); if (jsonStart === -1) throw new Error('no JSON in CLI output'); const data = JSON.parse(raw.slice(jsonStart)); // Overlay real ADR count from both local directories (fast, no network). data.adrs = getLocalADRCount(); writeCache(data); return data; } catch { /* CLI unavailable or timed out */ } // Fallback: use local file probes only (will be less accurate, but non-zero // when CLI is available and accurate when it's not). return buildLocalFallback(); } // Count ADRs from BOTH known directories (fix for ruflo#2195: old code missed // v3/docs/adr/ which holds ADR-088..ADR-137, i.e. 41 of the 128 total ADRs). function getLocalADRCount() { const adrDirs = [ path.join(CWD, 'v3', 'implementation', 'adrs'), path.join(CWD, 'v3', 'docs', 'adr'), path.join(CWD, 'docs', 'adrs'), path.join(CWD, '.claude-flow', 'adrs'), ]; let total = 0; for (const dir of adrDirs) { try { if (fs.existsSync(dir)) { const files = fs.readdirSync(dir).filter(function(f) { return f.endsWith('.md') && (f.startsWith('ADR-') || f.startsWith('adr-') || /^\\d{4}-/.test(f)); }); total += files.length; } } catch { /* ignore */ } } return { count: total, implemented: total, compliance: 0 }; } // Minimal local fallback when the CLI is not installed or times out. // Returns a structure that matches the CLI JSON schema so the renderer works. function buildLocalFallback() { const memMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024); const adrs = getLocalADRCount(); // Test file count (pure directory walk, no file reads) let testFiles = 0; function countTests(dir, depth) { if ((depth || 0) > 4) return; try { if (!fs.existsSync(dir)) return; for (const e of fs.readdirSync(dir, { withFileTypes: true })) { if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') { countTests(path.join(dir, e.name), (depth || 0) + 1); } else if (e.isFile() && (e.name.includes('.test.') || e.name.includes('.spec.') || e.name.startsWith('test_') || e.name.startsWith('spec_'))) { testFiles++; } } } catch { /* ignore */ } } for (const d of ['tests', 'test', '__tests__', 'src', 'v3']) countTests(path.join(CWD, d)); return { user: { name: 'user', gitBranch: '', modelName: 'Claude Code' }, v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 }, security: { status: 'NONE', cvesFixed: 0, totalCves: 0 }, swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false }, system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 }, adrs, hooks: { enabled: 0, total: 0 }, agentdb: { vectorCount: 0, dbSizeKB: 0, hasHnsw: false }, tests: { testFiles, testCases: testFiles * 4 }, lastUpdated: new Date().toISOString(), }; } // ANSI colors const c = { reset: '\\x1b[0m', bold: '\\x1b[1m', dim: '\\x1b[2m', red: '\\x1b[0;31m', green: '\\x1b[0;32m', yellow: '\\x1b[0;33m', blue: '\\x1b[0;34m', purple: '\\x1b[0;35m', cyan: '\\x1b[0;36m', brightRed: '\\x1b[1;31m', brightGreen: '\\x1b[1;32m', brightYellow: '\\x1b[1;33m', brightBlue: '\\x1b[1;34m', brightPurple: '\\x1b[1;35m', brightCyan: '\\x1b[1;36m', brightWhite: '\\x1b[1;37m', }; // Safe execSync with strict timeout (returns empty string on failure) function safeExec(cmd, timeoutMs) { try { return execSync(cmd, { encoding: 'utf-8', timeout: timeoutMs || 2000, stdio: ['pipe', 'pipe', 'pipe'], }).trim(); } catch { return ''; } } // Safe JSON file reader (returns null on failure) function readJSON(filePath) { try { if (fs.existsSync(filePath)) { return JSON.parse(fs.readFileSync(filePath, 'utf-8')); } } catch { /* ignore */ } return null; } // ─── Git info (pure-Node / single exec — needed for branch display) ────────── function getGitInfo() { const result = { name: 'user', gitBranch: '', modified: 0, untracked: 0, staged: 0, ahead: 0, behind: 0, }; const script = [ 'git config user.name 2>/dev/null || echo user', 'echo "---SEP---"', 'git branch --show-current 2>/dev/null', 'echo "---SEP---"', 'git status --porcelain 2>/dev/null', 'echo "---SEP---"', 'git rev-list --left-right --count HEAD...@{upstream} 2>/dev/null || echo "0 0"', ].join('; '); const raw = safeExec("sh -c '" + script + "'", 3000); if (!raw) return result; const parts = raw.split('---SEP---').map(function(s) { return s.trim(); }); if (parts.length >= 4) { result.name = parts[0] || 'user'; result.gitBranch = parts[1] || ''; if (parts[2]) { for (const line of parts[2].split('\\n')) { if (!line || line.length < 2) continue; const x = line[0], y = line[1]; if (x === '?' && y === '?') { result.untracked++; continue; } if (x !== ' ' && x !== '?') result.staged++; if (y !== ' ' && y !== '?') result.modified++; } } const ab = (parts[3] || '0 0').split(/\\s+/); result.ahead = parseInt(ab[0]) || 0; result.behind = parseInt(ab[1]) || 0; } return result; } // Detect model name from Claude config (pure file reads, no exec) function getModelName() { try { const claudeConfig = readJSON(path.join(os.homedir(), '.claude.json')); if (claudeConfig && claudeConfig.projects) { for (const [projectPath, projectConfig] of Object.entries(claudeConfig.projects)) { if (CWD === projectPath || CWD.startsWith(projectPath + '/')) { const usage = projectConfig.lastModelUsage; if (usage) { const ids = Object.keys(usage); if (ids.length > 0) { let modelId = ids[ids.length - 1]; let latest = 0; for (const id of ids) { const ts = usage[id] && usage[id].lastUsedAt ? new Date(usage[id].lastUsedAt).getTime() : 0; if (ts > latest) { latest = ts; modelId = id; } } if (modelId.includes('opus')) return 'Opus 4.8'; if (modelId.includes('sonnet')) return 'Sonnet 4.6'; if (modelId.includes('haiku')) return 'Haiku 4.5'; return modelId.split('-').slice(1, 3).join(' '); } } break; } } } } catch { /* ignore */ } // Fallback: settings.json model field const settings = getSettings(); if (settings && settings.model) { const m = settings.model; if (m.includes('opus')) return 'Opus 4.8'; if (m.includes('sonnet')) return 'Sonnet 4.6'; if (m.includes('haiku')) return 'Haiku 4.5'; } return 'Claude Code'; } // ─── Stdin reader (Claude Code pipes session JSON) ────────────── // Claude Code sends session JSON via stdin. Read synchronously so the // script works both when invoked by Claude Code (stdin has JSON) and // when run manually from terminal (stdin is empty/tty). let _stdinData = null; function getStdinData() { if (_stdinData !== undefined && _stdinData !== null) return _stdinData; try { if (process.stdin.isTTY) { _stdinData = null; return null; } const chunks = []; const buf = Buffer.alloc(4096); let bytesRead; try { while ((bytesRead = fs.readSync(0, buf, 0, buf.length, null)) > 0) { chunks.push(buf.slice(0, bytesRead)); } } catch { /* EOF or read error */ } const raw = Buffer.concat(chunks).toString('utf-8').trim(); _stdinData = (raw && raw.startsWith('{')) ? JSON.parse(raw) : null; } catch { _stdinData = null; } return _stdinData; } function getModelFromStdin() { const data = getStdinData(); return (data && data.model && data.model.display_name) ? data.model.display_name : null; } function getContextFromStdin() { const data = getStdinData(); if (data && data.context_window) { return { usedPct: Math.floor(data.context_window.used_percentage || 0) }; } return null; } function getCostFromStdin() { const data = getStdinData(); if (data && data.cost) { const durationMs = data.cost.total_duration_ms || 0; const mins = Math.floor(durationMs / 60000); const secs = Math.floor((durationMs % 60000) / 1000); return { costUsd: data.cost.total_cost_usd || 0, duration: mins > 0 ? mins + 'm' + secs + 's' : secs + 's', }; } return null; } // Read package version from the first package.json we find. function getPkgVersion() { let ver = '3.6'; try { const home = os.homedir(); const pkgPaths = [ path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'package.json'), path.join(CWD, 'node_modules', '@claude-flow', 'cli', 'package.json'), path.join(CWD, 'node_modules', 'ruflo', 'package.json'), path.join(CWD, 'v3', '@claude-flow', 'cli', 'package.json'), ]; // #2221: global installs (npm i -g ruflo) live outside CWD/node_modules, so the // probes above all miss and the version falls back to the hard-coded default. // Derive the global node_modules dir from the running node binary (no npm spawn — // statusline renders often). Covers nvm/mise (bin/../lib/node_modules) and Windows // (bin/node_modules) layouts. try { const binDir = path.dirname(process.execPath); for (const gm of [path.join(binDir, '..', 'lib', 'node_modules'), path.join(binDir, 'node_modules')]) { pkgPaths.push( path.join(gm, 'ruflo', 'package.json'), path.join(gm, '@claude-flow', 'cli', 'package.json'), ); } } catch { /* ignore */ } for (const p of pkgPaths) { if (!fs.existsSync(p)) continue; try { const pkg = JSON.parse(fs.readFileSync(p, 'utf-8')); if (pkg && typeof pkg.version === 'string' && pkg.version.length > 0) { ver = pkg.version; break; } } catch { /* ignore */ } } } catch { /* ignore */ } return ver; } // ─── Rendering ────────────────────────────────────────────────── function progressBar(current, total) { const width = 5; const filled = Math.round((current / total) * width); return '[' + '●'.repeat(filled) + '○'.repeat(width - filled) + ']'; } function generateStatusline() { const d = getStatuslineData(); const git = getGitInfo(); const modelName = getModelFromStdin() || (d.user && d.user.modelName) || 'Claude Code'; const ctxInfo = getContextFromStdin(); const costInfo = getCostFromStdin(); const pkgVersion = getPkgVersion(); const progress = d.v3Progress || {}; const security = d.security || {}; const swarm = d.swarm || {}; const system = d.system || {}; const adrs = d.adrs || {}; const hooks = d.hooks || {}; const agentdb = d.agentdb || {}; const tests = d.tests || {}; const domainsCompleted = progress.domainsCompleted || 0; const totalDomains = progress.totalDomains || 5; const dddProgress = progress.dddProgress || 0; const patternsLearned = progress.patternsLearned || 0; const activeAgents = swarm.activeAgents || 0; const maxAgents = swarm.maxAgents || CONFIG.maxAgents; const coordinationActive = swarm.coordinationActive || false; const intelligencePct = system.intelligencePct || 0; const memoryMB = system.memoryMB || 0; const subAgents = system.subAgents || 0; const cvesFixed = security.cvesFixed || 0; const totalCves = security.totalCves || 0; const secStatus = security.status || 'NONE'; const adrCount = adrs.count || 0; const adrImpl = adrs.implemented || 0; const hooksEnabled = hooks.enabled || 0; const hooksTotal = hooks.total || 0; const vectorCount = agentdb.vectorCount || 0; const hasHnsw = agentdb.hasHnsw || false; const dbSizeKB = agentdb.dbSizeKB || 0; const testFiles = tests.testFiles || 0; const testCases = tests.testCases || testFiles * 4; const lines = []; // Header let header = c.bold + c.brightPurple + '▊ RuFlo V' + pkgVersion + ' ' + c.reset; header += (coordinationActive ? c.brightCyan : c.dim) + '● ' + c.brightCyan + git.name + c.reset; if (git.gitBranch) { header += ' ' + c.dim + '│' + c.reset + ' ' + c.brightBlue + '⏇ ' + git.gitBranch + c.reset; const changes = git.modified + git.staged + git.untracked; if (changes > 0) { let ind = ''; if (git.staged > 0) ind += c.brightGreen + '+' + git.staged + c.reset; if (git.modified > 0) ind += c.brightYellow + '~' + git.modified + c.reset; if (git.untracked > 0) ind += c.dim + '?' + git.untracked + c.reset; header += ' ' + ind; } if (git.ahead > 0) header += ' ' + c.brightGreen + '↑' + git.ahead + c.reset; if (git.behind > 0) header += ' ' + c.brightRed + '↓' + git.behind + c.reset; } header += ' ' + c.dim + '│' + c.reset + ' ' + c.purple + modelName + c.reset; const duration = costInfo ? costInfo.duration : ''; if (duration) header += ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + '⏱ ' + duration + c.reset; if (ctxInfo && ctxInfo.usedPct > 0) { const ctxColor = ctxInfo.usedPct >= 90 ? c.brightRed : ctxInfo.usedPct >= 70 ? c.brightYellow : c.brightGreen; header += ' ' + c.dim + '│' + c.reset + ' ' + ctxColor + '● ' + ctxInfo.usedPct + '% ctx' + c.reset; } if (!CONFIG.hideCost && costInfo && costInfo.costUsd > 0) { header += ' ' + c.dim + '│' + c.reset + ' ' + c.brightYellow + CONFIG.costSymbol + costInfo.costUsd.toFixed(2) + c.reset; } lines.push(header); // Separator lines.push(c.dim + '─'.repeat(53) + c.reset); // Line 1: DDD Domains const domainsColor = domainsCompleted >= 3 ? c.brightGreen : domainsCompleted > 0 ? c.yellow : c.red; let perfIndicator; if (hasHnsw && vectorCount > 0) { const speedup = vectorCount > 10000 ? '12500x' : vectorCount > 1000 ? '150x' : '10x'; perfIndicator = c.brightGreen + '⚡ HNSW ' + speedup + c.reset; } else if (patternsLearned > 0) { const pk = patternsLearned >= 1000 ? (patternsLearned / 1000).toFixed(1) + 'k' : String(patternsLearned); perfIndicator = c.brightYellow + '📚 ' + pk + ' patterns' + c.reset; } else { perfIndicator = c.dim + '⚡ target: 150x-12500x' + c.reset; } lines.push( c.brightCyan + '🏗️ DDD Domains' + c.reset + ' ' + progressBar(domainsCompleted, totalDomains) + ' ' + domainsColor + domainsCompleted + c.reset + '/' + c.brightWhite + totalDomains + c.reset + ' ' + perfIndicator ); // Line 2: Swarm + Hooks + CVE + Memory + Intelligence const swarmInd = coordinationActive ? c.brightGreen + '◉' + c.reset : c.dim + '○' + c.reset; const agentsColor = activeAgents > 0 ? c.brightGreen : c.red; const secIcon = secStatus === 'CLEAN' ? '🟢' : (secStatus === 'IN_PROGRESS' || secStatus === 'STALE') ? '🟡' : (secStatus === 'NONE' ? '⚪' : '🔴'); const secColor = secStatus === 'CLEAN' ? c.brightGreen : (secStatus === 'IN_PROGRESS' || secStatus === 'STALE') ? c.brightYellow : (secStatus === 'NONE' ? c.dim : c.brightRed); const hooksColor = hooksEnabled > 0 ? c.brightGreen : c.dim; const intellColor = intelligencePct >= 80 ? c.brightGreen : intelligencePct >= 40 ? c.brightYellow : c.dim; lines.push( c.brightYellow + '🤖 Swarm' + c.reset + ' ' + swarmInd + ' [' + agentsColor + String(activeAgents).padStart(2) + c.reset + '/' + c.brightWhite + maxAgents + c.reset + '] ' + c.brightPurple + '👥 ' + subAgents + c.reset + ' ' + c.brightBlue + '🪝 ' + hooksColor + hooksEnabled + c.reset + '/' + c.brightWhite + hooksTotal + c.reset + ' ' + secIcon + ' ' + secColor + 'CVE ' + cvesFixed + c.reset + '/' + c.brightWhite + totalCves + c.reset + ' ' + c.brightCyan + '💾 ' + memoryMB + 'MB' + c.reset + ' ' + intellColor + '🧠 ' + String(intelligencePct).padStart(3) + '%' + c.reset ); // Line 3: Architecture const dddColor = dddProgress >= 50 ? c.brightGreen : dddProgress > 0 ? c.yellow : c.red; const adrColor = adrCount > 0 ? (adrImpl === adrCount ? c.brightGreen : c.yellow) : c.dim; const adrDisplay = adrColor + '●' + adrImpl + '/' + adrCount + c.reset; lines.push( c.brightPurple + '🔧 Architecture' + c.reset + ' ' + c.cyan + 'ADRs' + c.reset + ' ' + adrDisplay + ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + 'DDD' + c.reset + ' ' + dddColor + '●' + String(dddProgress).padStart(3) + '%' + c.reset + ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + 'Security' + c.reset + ' ' + secColor + '●' + secStatus + c.reset ); // Line 4: AgentDB, Tests, Integration const hnswInd = hasHnsw ? c.brightGreen + '⚡' + c.reset : ''; const sizeDisp = dbSizeKB >= 1024 ? (dbSizeKB / 1024).toFixed(1) + 'MB' : dbSizeKB + 'KB'; const vectorColor = vectorCount > 0 ? c.brightGreen : c.dim; const testColor = testFiles > 0 ? c.brightGreen : c.dim; // MCP / DB integration from data const integration = d.integration || {}; const mcpServers = (integration.mcpServers) || {}; let integStr = ''; if (mcpServers.total > 0) { const mcpCol = mcpServers.enabled === mcpServers.total ? c.brightGreen : mcpServers.enabled > 0 ? c.brightYellow : c.red; integStr += c.cyan + 'MCP' + c.reset + ' ' + mcpCol + '●' + mcpServers.enabled + '/' + mcpServers.total + c.reset; } if (integration.hasDatabase) integStr += (integStr ? ' ' : '') + c.brightGreen + '◆' + c.reset + 'DB'; if (!integStr) integStr = c.dim + '● none' + c.reset; lines.push( c.brightCyan + '📊 AgentDB' + c.reset + ' ' + c.cyan + 'Vectors' + c.reset + ' ' + vectorColor + '●' + vectorCount + hnswInd + c.reset + ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + 'Size' + c.reset + ' ' + c.brightWhite + sizeDisp + c.reset + ' ' + c.dim + '│' + c.reset + ' ' + c.cyan + 'Tests' + c.reset + ' ' + testColor + '●' + testFiles + c.reset + ' ' + c.dim + '(~' + testCases + ' cases)' + c.reset + ' ' + c.dim + '│' + c.reset + ' ' + integStr ); return lines.join('\\n'); } // JSON output — delegates to CLI for accuracy; caller can use --json flag function generateJSON() { const d = getStatuslineData(); const git = getGitInfo(); return Object.assign({}, d, { user: Object.assign({ name: git.name, gitBranch: git.gitBranch }, d.user || {}), git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind }, lastUpdated: new Date().toISOString(), }); } // ─── Main ─────────────────────────────────────────────────────── if (process.argv.includes('--json')) { console.log(JSON.stringify(generateJSON(), null, 2)); } else if (process.argv.includes('--compact')) { console.log(JSON.stringify(generateJSON())); } else { console.log(generateStatusline()); } `; } /** * Generate statusline hook for shell integration */ export function generateStatuslineHook(options) { if (!options.statusline.enabled) { return '#!/bin/bash\n# Statusline disabled\n'; } return `#!/bin/bash # RuFlo V3 Statusline Hook # Source this in your .bashrc/.zshrc for terminal statusline # Function to get statusline claude_flow_statusline() { local statusline_script="\${CLAUDE_FLOW_DIR:-.claude}/helpers/statusline.cjs" if [ -f "$statusline_script" ]; then node "$statusline_script" 2>/dev/null || echo "" fi } # Bash: Add to PS1 # export PS1='$(claude_flow_statusline) \\n\\$ ' # Zsh: Add to RPROMPT # export RPROMPT='$(claude_flow_statusline)' # Claude Code: Add to .claude/settings.json # "statusLine": { # "type": "command", # "command": "node .claude/helpers/statusline.cjs 2>/dev/null" # "when": "test -f .claude/helpers/statusline.cjs" # } `; } //# sourceMappingURL=statusline-generator.js.map