🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

devflow-kit

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

devflow-kit - npm Package Compare versions

Comparing version
1.7.0
to
1.8.0
+22
dist/commands/hud.d.ts
import { Command } from 'commander';
/**
* Add the HUD statusLine to settings JSON.
* Idempotent — returns unchanged JSON if HUD already set.
* Upgrades legacy statusline.sh to hud.sh automatically.
*/
export declare function addHudStatusLine(settingsJson: string, devflowDir: string): string;
/**
* Remove the HUD statusLine from settings JSON.
* Idempotent — returns unchanged JSON if statusLine not present or not DevFlow.
*/
export declare function removeHudStatusLine(settingsJson: string): string;
/**
* Check if the statusLine in settings JSON points to the DevFlow HUD.
*/
export declare function hasHudStatusLine(settingsJson: string): boolean;
/**
* Check if an existing statusLine belongs to a non-DevFlow tool.
*/
export declare function hasNonDevFlowStatusLine(settingsJson: string): boolean;
export declare const hudCommand: Command;
//# sourceMappingURL=hud.d.ts.map
import { Command } from 'commander';
import { promises as fs } from 'node:fs';
import * as path from 'node:path';
import * as p from '@clack/prompts';
import color from 'picocolors';
import { getClaudeDirectory, getDevFlowDirectory } from '../utils/paths.js';
import { HUD_COMPONENTS, loadConfig, saveConfig, } from '../hud/config.js';
/**
* Add the HUD statusLine to settings JSON.
* Idempotent — returns unchanged JSON if HUD already set.
* Upgrades legacy statusline.sh to hud.sh automatically.
*/
export function addHudStatusLine(settingsJson, devflowDir) {
const settings = JSON.parse(settingsJson);
const hudCommand = path.join(devflowDir, 'scripts', 'hud.sh');
// Already pointing to this exact HUD — nothing to do
if (settings.statusLine?.command === hudCommand) {
return settingsJson;
}
// If there's a non-DevFlow statusLine, don't overwrite (caller should check first)
if (settings.statusLine && !isDevFlowStatusLine(settings.statusLine)) {
return settingsJson;
}
settings.statusLine = {
type: 'command',
command: hudCommand,
};
return JSON.stringify(settings, null, 2) + '\n';
}
/**
* Remove the HUD statusLine from settings JSON.
* Idempotent — returns unchanged JSON if statusLine not present or not DevFlow.
*/
export function removeHudStatusLine(settingsJson) {
const settings = JSON.parse(settingsJson);
if (!settings.statusLine) {
return settingsJson;
}
// Only remove if it's a DevFlow HUD/statusline
if (!isDevFlowStatusLine(settings.statusLine)) {
return settingsJson;
}
delete settings.statusLine;
return JSON.stringify(settings, null, 2) + '\n';
}
/**
* Check if the statusLine in settings JSON points to the DevFlow HUD.
*/
export function hasHudStatusLine(settingsJson) {
const settings = JSON.parse(settingsJson);
if (!settings.statusLine)
return false;
return isDevFlowStatusLine(settings.statusLine);
}
/**
* Check if an existing statusLine belongs to DevFlow (HUD or legacy statusline).
* Matches paths containing 'hud.sh', 'statusline.sh', or a '/devflow/' directory segment.
*/
function isDevFlowStatusLine(statusLine) {
const cmd = statusLine.command ?? '';
return (cmd.includes('hud.sh') ||
cmd.includes('statusline.sh') ||
cmd.includes('/devflow/') ||
cmd.includes('\\devflow\\'));
}
/**
* Check if an existing statusLine belongs to a non-DevFlow tool.
*/
export function hasNonDevFlowStatusLine(settingsJson) {
const settings = JSON.parse(settingsJson);
if (!settings.statusLine?.command)
return false;
return !isDevFlowStatusLine(settings.statusLine);
}
export const hudCommand = new Command('hud')
.description('Configure the HUD (status line)')
.option('--status', 'Show current HUD config')
.option('--detail', 'Show tool/agent descriptions in HUD')
.option('--no-detail', 'Hide tool/agent descriptions')
.option('--enable', 'Enable HUD in settings')
.option('--disable', 'Disable HUD (remove statusLine)')
.action(async (options) => {
const hasFlag = options.status ||
options.enable ||
options.disable ||
options.detail !== undefined;
if (!hasFlag) {
p.intro(color.bgCyan(color.white(' HUD ')));
p.note(`${color.cyan('devflow hud --detail')} Show tool/agent descriptions\n` +
`${color.cyan('devflow hud --no-detail')} Hide tool/agent descriptions\n` +
`${color.cyan('devflow hud --status')} Show current config\n` +
`${color.cyan('devflow hud --enable')} Enable HUD in settings\n` +
`${color.cyan('devflow hud --disable')} Remove HUD from settings`, 'Usage');
p.note(`${HUD_COMPONENTS.length} components: ${HUD_COMPONENTS.join(', ')}`, 'Components');
p.outro(color.dim('Toggle with --enable / --disable'));
return;
}
if (options.status) {
const config = loadConfig();
p.intro(color.bgCyan(color.white(' HUD Status ')));
p.note(`${color.dim('Enabled:')} ${config.enabled ? color.green('yes') : color.dim('no')}\n` +
`${color.dim('Detail:')} ${config.detail ? color.green('on') : color.dim('off')}\n` +
`${color.dim('Components:')} ${HUD_COMPONENTS.length}`, 'Current config');
// Check settings.json
const claudeDir = getClaudeDirectory();
const settingsPath = path.join(claudeDir, 'settings.json');
try {
const content = await fs.readFile(settingsPath, 'utf-8');
const enabled = hasHudStatusLine(content);
p.log.info(`Status line: ${enabled ? color.green('enabled') : color.dim('disabled')}`);
}
catch {
p.log.info(`Status line: ${color.dim('no settings.json found')}`);
}
return;
}
if (options.detail !== undefined) {
const config = loadConfig();
config.detail = options.detail;
saveConfig(config);
p.log.success(`HUD detail ${config.detail ? 'enabled' : 'disabled'}`);
return;
}
if (options.enable) {
const claudeDir = getClaudeDirectory();
const settingsPath = path.join(claudeDir, 'settings.json');
let settingsContent;
try {
settingsContent = await fs.readFile(settingsPath, 'utf-8');
}
catch {
settingsContent = '{}';
}
// Ensure statusLine is registered
if (!hasHudStatusLine(settingsContent)) {
// Check for non-DevFlow statusLine
if (hasNonDevFlowStatusLine(settingsContent)) {
const settings = JSON.parse(settingsContent);
p.log.warn(`Existing statusLine found: ${color.dim(settings.statusLine?.command ?? 'unknown')}`);
if (process.stdin.isTTY) {
const overwrite = await p.confirm({
message: 'Replace existing statusLine with DevFlow HUD?',
initialValue: false,
});
if (p.isCancel(overwrite) || !overwrite) {
p.log.info('HUD not enabled — existing statusLine preserved');
return;
}
}
else {
p.log.info('Non-interactive mode — skipping (existing statusLine would be overwritten)');
return;
}
}
const devflowDir = getDevFlowDirectory();
const updated = addHudStatusLine(settingsContent, devflowDir);
await fs.writeFile(settingsPath, updated, 'utf-8');
}
// Update config
const config = loadConfig();
if (config.enabled) {
p.log.info('HUD already enabled');
return;
}
saveConfig({ ...config, enabled: true });
p.log.success('HUD enabled');
p.log.info(color.dim('Restart Claude Code to see the HUD'));
}
if (options.disable) {
const config = loadConfig();
if (!config.enabled) {
p.log.info('HUD already disabled');
return;
}
saveConfig({ ...config, enabled: false });
p.log.success('HUD disabled');
p.log.info(color.dim('Version upgrade notifications will still appear'));
}
});
//# sourceMappingURL=hud.js.map
export declare function getCacheDir(): string;
/**
* Read a cached value. Returns null if missing or expired.
*/
export declare function readCache<T>(key: string): T | null;
/**
* Read a cached value regardless of TTL (stale data). Returns null if missing.
*/
export declare function readCacheStale<T>(key: string): T | null;
/**
* Write a value to cache with a TTL in milliseconds.
*/
export declare function writeCache<T>(key: string, data: T, ttlMs: number): void;
//# sourceMappingURL=cache.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
export function getCacheDir() {
const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow');
return path.join(devflowDir, 'cache');
}
/**
* Read a cached value. Returns null if missing or expired.
* When `ignoreExpiry` is true, returns data regardless of TTL (stale read).
*/
function readCacheEntry(key, ignoreExpiry) {
try {
const filePath = path.join(getCacheDir(), `${key}.json`);
const raw = fs.readFileSync(filePath, 'utf-8');
const entry = JSON.parse(raw);
if (ignoreExpiry || Date.now() - entry.timestamp < entry.ttl) {
return entry.data;
}
return null;
}
catch {
return null;
}
}
/**
* Read a cached value. Returns null if missing or expired.
*/
export function readCache(key) {
return readCacheEntry(key, false);
}
/**
* Read a cached value regardless of TTL (stale data). Returns null if missing.
*/
export function readCacheStale(key) {
return readCacheEntry(key, true);
}
/**
* Write a value to cache with a TTL in milliseconds.
*/
export function writeCache(key, data, ttlMs) {
try {
const dir = getCacheDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const entry = { data, timestamp: Date.now(), ttl: ttlMs };
fs.writeFileSync(path.join(dir, `${key}.json`), JSON.stringify(entry));
}
catch {
// Cache write failure is non-fatal
}
}
//# sourceMappingURL=cache.js.map
/**
* ANSI color helpers — no dependencies, precompiled escape sequences.
* Used by HUD components for direct terminal output (not @clack/prompts).
*/
export declare function bold(s: string): string;
export declare function dim(s: string): string;
export declare function red(s: string): string;
export declare function green(s: string): string;
export declare function yellow(s: string): string;
export declare function blue(s: string): string;
export declare function magenta(s: string): string;
export declare function cyan(s: string): string;
export declare function gray(s: string): string;
export declare function white(s: string): string;
export declare function orange(s: string): string;
export declare function brightRed(s: string): string;
export declare function boldRed(s: string): string;
export declare function bgGreen(s: string): string;
export declare function bgYellow(s: string): string;
export declare function bgRed(s: string): string;
export declare function truncate(s: string, max: number): string;
export declare function stripAnsi(s: string): string;
//# sourceMappingURL=colors.d.ts.map
/**
* ANSI color helpers — no dependencies, precompiled escape sequences.
* Used by HUD components for direct terminal output (not @clack/prompts).
*/
const ESC = '\x1b[';
const RESET = `${ESC}0m`;
export function bold(s) {
return `${ESC}1m${s}${RESET}`;
}
export function dim(s) {
return `${ESC}2m${s}${RESET}`;
}
export function red(s) {
return `${ESC}31m${s}${RESET}`;
}
export function green(s) {
return `${ESC}32m${s}${RESET}`;
}
export function yellow(s) {
return `${ESC}33m${s}${RESET}`;
}
export function blue(s) {
return `${ESC}34m${s}${RESET}`;
}
export function magenta(s) {
return `${ESC}35m${s}${RESET}`;
}
export function cyan(s) {
return `${ESC}36m${s}${RESET}`;
}
export function gray(s) {
return `${ESC}90m${s}${RESET}`;
}
export function white(s) {
return `${ESC}37m${s}${RESET}`;
}
export function orange(s) {
return `${ESC}38;5;208m${s}${RESET}`;
}
export function brightRed(s) {
return `${ESC}91m${s}${RESET}`;
}
export function boldRed(s) {
return `${ESC}1;31m${s}${RESET}`;
}
export function bgGreen(s) {
return `${ESC}42m${s}${RESET}`;
}
export function bgYellow(s) {
return `${ESC}43m${s}${RESET}`;
}
export function bgRed(s) {
return `${ESC}41m${s}${RESET}`;
}
export function truncate(s, max) {
return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
}
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
export function stripAnsi(s) {
return s.replace(ANSI_PATTERN, '');
}
//# sourceMappingURL=colors.js.map
import type { ComponentResult, GatherContext, ConfigCountsData } from '../types.js';
/**
* Gather configuration counts for the configCounts component.
* Exported for use by the main HUD entry point.
*/
export declare function gatherConfigCounts(cwd: string): ConfigCountsData;
export default function configCounts(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=config-counts.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { dim } from '../colors.js';
function countClaudeMdFiles(cwd) {
let count = 0;
// Check project CLAUDE.md
if (fs.existsSync(path.join(cwd, 'CLAUDE.md')))
count++;
// Check user CLAUDE.md
const claudeDir = process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude');
if (fs.existsSync(path.join(claudeDir, 'CLAUDE.md')))
count++;
return count;
}
function countFromSettings(settingsPath) {
try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
const mcpServers = settings.mcpServers
? Object.keys(settings.mcpServers).length
: 0;
let hooks = 0;
if (settings.hooks) {
const hooksObj = settings.hooks;
for (const event of Object.values(hooksObj)) {
if (Array.isArray(event))
hooks += event.length;
}
}
return { mcpServers, hooks };
}
catch {
return { mcpServers: 0, hooks: 0 };
}
}
/**
* Gather configuration counts for the configCounts component.
* Exported for use by the main HUD entry point.
*/
export function gatherConfigCounts(cwd) {
const claudeDir = process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude');
const claudeMdFiles = countClaudeMdFiles(cwd);
// Count rules (.md/.mdc files in .claude/rules)
let rules = 0;
for (const rulesDir of [
path.join(cwd, '.claude', 'rules'),
path.join(claudeDir, 'rules'),
]) {
try {
const files = fs.readdirSync(rulesDir);
rules += files.filter((f) => f.endsWith('.md') || f.endsWith('.mdc')).length;
}
catch {
/* ignore */
}
}
// Aggregate settings from user and project
const userSettings = countFromSettings(path.join(claudeDir, 'settings.json'));
const projectSettings = countFromSettings(path.join(cwd, '.claude', 'settings.json'));
return {
claudeMdFiles,
rules,
mcpServers: userSettings.mcpServers + projectSettings.mcpServers,
hooks: userSettings.hooks + projectSettings.hooks,
};
}
export default async function configCounts(ctx) {
if (!ctx.configCounts)
return null;
const { claudeMdFiles, rules: ruleCount, mcpServers, hooks: hookCount } = ctx.configCounts;
const skillCount = ctx.transcript?.skills.length ?? 0;
const parts = [];
if (claudeMdFiles > 0)
parts.push(`${claudeMdFiles} CLAUDE.md`);
if (ruleCount > 0)
parts.push(`${ruleCount} rules`);
if (mcpServers > 0)
parts.push(`${mcpServers} MCPs`);
if (hookCount > 0)
parts.push(`${hookCount} hooks`);
if (skillCount > 0)
parts.push(`${skillCount} skills`);
if (parts.length === 0)
return null;
const raw = parts.join(' \u00B7 ');
const text = parts.map((p) => dim(p)).join(dim(' \u00B7 '));
return { text, raw };
}
//# sourceMappingURL=config-counts.js.map
import type { ComponentResult, GatherContext } from '../types.js';
/**
* Context window usage component.
* Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red.
* At >85% appends token breakdown: (in: Nk).
*/
export default function contextUsage(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=context-usage.d.ts.map
import { dim, green, yellow, red } from '../colors.js';
const BAR_WIDTH = 8;
/**
* Context window usage component.
* Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red.
* At >85% appends token breakdown: (in: Nk).
*/
export default async function contextUsage(ctx) {
const cw = ctx.stdin.context_window;
if (!cw)
return null;
let pct = null;
if (cw.used_percentage !== undefined) {
pct = Math.round(cw.used_percentage);
}
else if (cw.context_window_size && cw.current_usage?.input_tokens !== undefined) {
pct = Math.round((cw.current_usage.input_tokens / cw.context_window_size) * 100);
}
if (pct === null)
return null;
const filled = Math.round((pct / 100) * BAR_WIDTH);
const empty = BAR_WIDTH - filled;
let colorFn;
if (pct < 50) {
colorFn = green;
}
else if (pct < 80) {
colorFn = yellow;
}
else {
colorFn = red;
}
const filledBar = '\u2588'.repeat(filled);
const emptyBar = '\u2591'.repeat(empty);
let suffix = '';
if (pct > 85 && cw.current_usage?.input_tokens !== undefined) {
const inK = Math.round(cw.current_usage.input_tokens / 1000);
suffix = ` (in: ${inK}k)`;
}
const raw = `Current Session ${filledBar}${emptyBar} ${pct}%${suffix}`;
const text = dim('Current Session ') +
colorFn(filledBar) +
dim(emptyBar) +
' ' +
colorFn(`${pct}%`) +
(suffix ? dim(suffix) : '');
return { text, raw };
}
//# sourceMappingURL=context-usage.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function diffStats(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=diff-stats.d.ts.map
import { dim, green, red } from '../colors.js';
export default async function diffStats(ctx) {
if (!ctx.git)
return null;
const { filesChanged, additions, deletions } = ctx.git;
if (filesChanged === 0 && additions === 0 && deletions === 0)
return null;
const filePart = filesChanged > 0
? `${filesChanged} file${filesChanged === 1 ? '' : 's'}`
: '';
const lineParts = [];
if (additions > 0)
lineParts.push(`+${additions}`);
if (deletions > 0)
lineParts.push(`-${deletions}`);
const sections = [];
const rawSections = [];
if (filePart) {
sections.push(dim(filePart));
rawSections.push(filePart);
}
if (lineParts.length > 0) {
const lineText = lineParts
.map((p) => (p.startsWith('+') ? green(p) : red(p)))
.join(' ');
sections.push(lineText);
rawSections.push(lineParts.join(' '));
}
if (sections.length === 0)
return null;
return {
text: sections.join(dim(' \u00B7 ')),
raw: rawSections.join(' \u00B7 '),
};
}
//# sourceMappingURL=diff-stats.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function directory(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=directory.d.ts.map
import { bold, white } from '../colors.js';
import * as path from 'node:path';
export default async function directory(ctx) {
const cwd = ctx.stdin.cwd;
if (!cwd)
return null;
const name = path.basename(cwd);
return { text: bold(white(name)), raw: name };
}
//# sourceMappingURL=directory.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function gitAheadBehind(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=git-ahead-behind.d.ts.map
import { dim } from '../colors.js';
export default async function gitAheadBehind(ctx) {
if (!ctx.git)
return null;
const { ahead, behind } = ctx.git;
if (ahead === 0 && behind === 0)
return null;
const rawParts = [];
if (ahead > 0)
rawParts.push(`${ahead}\u2191`);
if (behind > 0)
rawParts.push(`${behind}\u2193`);
const raw = rawParts.join(' ');
return { text: dim(raw), raw };
}
//# sourceMappingURL=git-ahead-behind.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function gitBranch(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=git-branch.d.ts.map
import { white, yellow, green } from '../colors.js';
export default async function gitBranch(ctx) {
if (!ctx.git)
return null;
const dirtyMark = ctx.git.dirty ? '*' : '';
const stagedMark = ctx.git.staged ? '+' : '';
const indicator = dirtyMark + stagedMark;
const text = white(ctx.git.branch) +
(dirtyMark ? yellow(dirtyMark) : '') +
(stagedMark ? green(stagedMark) : '');
const raw = ctx.git.branch + indicator;
return { text, raw };
}
//# sourceMappingURL=git-branch.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function model(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=model.d.ts.map
import { dim, white } from '../colors.js';
export default async function model(ctx) {
const name = ctx.stdin.model?.display_name;
if (!name)
return null;
// Strip "Claude " prefix and trailing context info like "(1M context)" for brevity
const short = name
.replace(/^Claude\s+/i, '')
.replace(/\s*\(\d+[KkMm]\s*context\)\s*$/, '');
const cwSize = ctx.stdin.context_window?.context_window_size;
let sizeStr = '';
if (cwSize) {
sizeStr =
cwSize >= 1_000_000
? ` [${Math.round(cwSize / 1_000_000)}m]`
: ` [${Math.round(cwSize / 1000)}k]`;
}
const raw = short + sizeStr;
return { text: white(short) + (sizeStr ? dim(sizeStr) : ''), raw };
}
//# sourceMappingURL=model.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function releaseInfo(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=release-info.d.ts.map
import { dim } from '../colors.js';
export default async function releaseInfo(ctx) {
if (!ctx.git?.lastTag)
return null;
const { lastTag, commitsSinceTag } = ctx.git;
const raw = commitsSinceTag > 0 ? `${lastTag} +${commitsSinceTag}` : lastTag;
return { text: dim(raw), raw };
}
//# sourceMappingURL=release-info.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function sessionCost(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=session-cost.d.ts.map
import { dim } from '../colors.js';
export default async function sessionCost(ctx) {
const cost = ctx.stdin.cost?.total_cost_usd;
if (cost == null)
return null;
const formatted = `$${cost.toFixed(2)}`;
return { text: dim(formatted), raw: formatted };
}
//# sourceMappingURL=session-cost.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function sessionDuration(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=session-duration.d.ts.map
import { dim } from '../colors.js';
export default async function sessionDuration(ctx) {
if (!ctx.sessionStartTime)
return null;
const elapsed = Math.floor((Date.now() - ctx.sessionStartTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const hours = Math.floor(minutes / 60);
let label;
if (hours > 0) {
label = `${hours}h ${minutes % 60}m`;
}
else {
label = `${minutes}m`;
}
const text = `\u23F1 ${label}`;
return { text: dim(text), raw: text };
}
//# sourceMappingURL=session-duration.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function todoProgress(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=todo-progress.d.ts.map
import { dim } from '../colors.js';
export default async function todoProgress(ctx) {
if (!ctx.transcript)
return null;
const { todos } = ctx.transcript;
if (todos.total === 0)
return null;
const label = `${todos.completed}/${todos.total} todos`;
return { text: dim(label), raw: label };
}
//# sourceMappingURL=todo-progress.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function usageQuota(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=usage-quota.d.ts.map
import { green, yellow, red, dim } from '../colors.js';
const BAR_WIDTH = 8;
function renderBar(percent) {
const filled = Math.round((percent / 100) * BAR_WIDTH);
const empty = BAR_WIDTH - filled;
let colorFn;
if (percent < 50) {
colorFn = green;
}
else if (percent < 80) {
colorFn = yellow;
}
else {
colorFn = red;
}
const filledBar = '\u2588'.repeat(filled);
const emptyBar = '\u2591'.repeat(empty);
const text = colorFn(filledBar) +
dim(emptyBar) +
' ' +
colorFn(`${percent}%`);
const raw = `${filledBar}${emptyBar} ${percent}%`;
return { text, raw };
}
export default async function usageQuota(ctx) {
if (!ctx.usage)
return null;
const { fiveHourPercent, sevenDayPercent } = ctx.usage;
const parts = [];
if (fiveHourPercent !== null) {
const bar = renderBar(Math.round(fiveHourPercent));
parts.push({ text: dim('5h ') + bar.text, raw: `5h ${bar.raw}` });
}
if (sevenDayPercent !== null) {
const bar = renderBar(Math.round(sevenDayPercent));
parts.push({ text: dim('7d ') + bar.text, raw: `7d ${bar.raw}` });
}
if (parts.length === 0)
return null;
const sep = dim(' \u00B7 ');
const text = dim('Session ') + parts.map((p) => p.text).join(sep);
const raw = 'Session ' + parts.map((p) => p.raw).join(' \u00B7 ');
return { text, raw };
}
//# sourceMappingURL=usage-quota.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function versionBadge(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=version-badge.d.ts.map
import { execFile } from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { yellow } from '../colors.js';
import { readCache, writeCache } from '../cache.js';
const VERSION_CACHE_KEY = 'version-check';
const VERSION_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
function getCurrentVersion(devflowDir) {
// Try manifest.json first (most reliable for installed version)
try {
const manifestPath = path.join(devflowDir, 'manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
if (typeof manifest.version === 'string')
return manifest.version;
}
catch {
// Fall through
}
// Try package.json as fallback
try {
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
if (typeof pkg.version === 'string')
return pkg.version;
}
catch {
// Fall through
}
return null;
}
function fetchLatestVersion() {
return new Promise((resolve) => {
execFile('npm', ['view', 'devflow-kit', 'version', '--json'], { timeout: 5000 }, (err, stdout) => {
if (err) {
resolve(null);
return;
}
try {
const parsed = JSON.parse(stdout.trim());
resolve(typeof parsed === 'string' ? parsed : null);
}
catch {
const trimmed = stdout.trim();
resolve(trimmed || null);
}
});
});
}
function compareVersions(current, latest) {
const a = current.split('.').map(Number);
const b = latest.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((a[i] || 0) < (b[i] || 0))
return -1;
if ((a[i] || 0) > (b[i] || 0))
return 1;
}
return 0;
}
export default async function versionBadge(ctx) {
const current = getCurrentVersion(ctx.devflowDir);
if (!current)
return null;
// Check cache
let info = readCache(VERSION_CACHE_KEY);
if (!info) {
const latest = await fetchLatestVersion();
if (latest) {
info = { current, latest };
writeCache(VERSION_CACHE_KEY, info, VERSION_CACHE_TTL);
}
}
if (info && compareVersions(info.current, info.latest) < 0) {
const badge = `\u2726 Devflow v${info.latest} \u00B7 update: npx devflow-kit init`;
return { text: yellow(badge), raw: badge };
}
// Don't show version when up to date
return null;
}
//# sourceMappingURL=version-badge.js.map
import type { ComponentResult, GatherContext } from '../types.js';
export default function worktreeCount(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=worktree-count.d.ts.map
import { dim } from '../colors.js';
export default async function worktreeCount(ctx) {
if (!ctx.git || ctx.git.worktreeCount <= 1)
return null;
const raw = `${ctx.git.worktreeCount} worktrees`;
return { text: dim(raw), raw };
}
//# sourceMappingURL=worktree-count.js.map
import type { HudConfig, ComponentId } from './types.js';
/**
* All 14 HUD components in display order.
*/
export declare const HUD_COMPONENTS: readonly ComponentId[];
export declare function getConfigPath(): string;
export declare function loadConfig(): HudConfig;
export declare function saveConfig(config: HudConfig): void;
export declare function resolveComponents(config: HudConfig): ComponentId[];
//# sourceMappingURL=config.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
/**
* All 14 HUD components in display order.
*/
export const HUD_COMPONENTS = [
'directory',
'gitBranch',
'gitAheadBehind',
'diffStats',
'releaseInfo',
'worktreeCount',
'model',
'contextUsage',
'versionBadge',
'sessionDuration',
'sessionCost',
'usageQuota',
'todoProgress',
'configCounts',
];
export function getConfigPath() {
const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow');
return path.join(devflowDir, 'hud.json');
}
export function loadConfig() {
const configPath = getConfigPath();
try {
const raw = fs.readFileSync(configPath, 'utf-8');
const parsed = JSON.parse(raw);
return {
enabled: parsed.enabled !== false,
detail: parsed.detail === true,
};
}
catch {
return { enabled: true, detail: false };
}
}
export function saveConfig(config) {
const configPath = getConfigPath();
const dir = path.dirname(configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
}
export function resolveComponents(config) {
if (config.enabled)
return [...HUD_COMPONENTS];
// Version badge always renders so users see upgrade notifications
return ['versionBadge'];
}
//# sourceMappingURL=config.js.map
export interface OAuthCredentials {
accessToken: string;
subscriptionType?: string;
}
/** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */
export declare function getClaudeDir(): string;
/** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */
export declare function readCredentialsFile(claudeDir?: string): OAuthCredentials | null;
/** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */
export declare function readKeychainToken(): Promise<string | null>;
/**
* Get OAuth credentials using platform-appropriate strategy.
* macOS: Keychain first, then file fallback. Other platforms: file only.
* Hybrid: if Keychain has token but no subscriptionType, merge from file.
*/
export declare function getCredentials(): Promise<OAuthCredentials | null>;
//# sourceMappingURL=credentials.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { execFile } from 'node:child_process';
const KEYCHAIN_TIMEOUT = 3000; // 3s
const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG;
function debugLog(msg, data) {
if (!DEBUG)
return;
const entry = { ts: new Date().toISOString(), source: 'credentials', msg, ...data };
fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n');
}
/** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */
export function getClaudeDir() {
return (process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude'));
}
/** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */
export function readCredentialsFile(claudeDir) {
try {
const dir = claudeDir ?? getClaudeDir();
const filePath = path.join(dir, '.credentials.json');
const raw = fs.readFileSync(filePath, 'utf-8');
const creds = JSON.parse(raw);
const oauth = creds.claudeAiOauth;
const accessToken = oauth?.accessToken;
if (typeof accessToken !== 'string' || !accessToken)
return null;
const subscriptionType = typeof oauth?.subscriptionType === 'string' ? oauth.subscriptionType : undefined;
debugLog('credentials file read', { filePath, hasSubscriptionType: !!subscriptionType });
return { accessToken, subscriptionType };
}
catch {
return null;
}
}
/** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */
export function readKeychainToken() {
if (process.platform !== 'darwin')
return Promise.resolve(null);
return new Promise((resolve) => {
execFile('/usr/bin/security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: KEYCHAIN_TIMEOUT }, (err, stdout) => {
if (err || !stdout.trim()) {
debugLog('keychain read failed', { error: err?.message });
resolve(null);
return;
}
try {
const parsed = JSON.parse(stdout.trim());
const oauth = parsed.claudeAiOauth;
const token = oauth?.accessToken;
if (typeof token === 'string' && token) {
debugLog('keychain token found');
resolve(token);
}
else {
debugLog('keychain: no accessToken in parsed data');
resolve(null);
}
}
catch {
// Keychain value might be the raw token string
const trimmed = stdout.trim();
if (trimmed.length > 20) {
debugLog('keychain: raw token string');
resolve(trimmed);
}
else {
debugLog('keychain: unparseable value');
resolve(null);
}
}
});
});
}
/**
* Get OAuth credentials using platform-appropriate strategy.
* macOS: Keychain first, then file fallback. Other platforms: file only.
* Hybrid: if Keychain has token but no subscriptionType, merge from file.
*/
export async function getCredentials() {
const fileCreds = readCredentialsFile();
if (process.platform !== 'darwin') {
debugLog('non-darwin: file credentials only', { found: !!fileCreds });
return fileCreds;
}
// macOS: try Keychain first
const keychainToken = await readKeychainToken();
if (keychainToken) {
// Merge subscriptionType from file if Keychain doesn't have it
const subscriptionType = fileCreds?.subscriptionType;
debugLog('using keychain token', { hasSubscriptionType: !!subscriptionType });
return { accessToken: keychainToken, subscriptionType };
}
// Fallback to file
debugLog('keychain failed, falling back to file', { found: !!fileCreds });
return fileCreds;
}
//# sourceMappingURL=credentials.js.map
import type { GitStatus } from './types.js';
/**
* Gather git status for the given working directory.
* Returns null if not in a git repo or on error.
*/
export declare function gatherGitStatus(cwd: string): Promise<GitStatus | null>;
//# sourceMappingURL=git.d.ts.map
import { execFile } from 'node:child_process';
const GIT_TIMEOUT = 1000; // 1s per command
function shellExec(cmd, args, cwd) {
return new Promise((resolve) => {
execFile(cmd, args, { cwd, timeout: GIT_TIMEOUT }, (err, stdout) => {
resolve(err ? '' : stdout.trim());
});
});
}
function gitExec(args, cwd) {
return shellExec('git', args, cwd);
}
/**
* Gather git status for the given working directory.
* Returns null if not in a git repo or on error.
*/
export async function gatherGitStatus(cwd) {
// Check if in a git repo
const topLevel = await gitExec(['rev-parse', '--show-toplevel'], cwd);
if (!topLevel)
return null;
// Branch name
const branch = await gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
if (!branch)
return null;
// Dirty check
const statusOutput = await gitExec(['status', '--porcelain', '--no-optional-locks'], cwd);
let dirty = false;
let staged = false;
for (const line of statusOutput.split('\n')) {
if (line.length < 2)
continue;
const index = line[0];
const worktree = line[1];
// Index column: staged change (A/M/D/R/C)
if (index !== ' ' && index !== '?')
staged = true;
// Worktree column: unstaged change (M/D), or untracked (??)
if (worktree !== ' ' || index === '?')
dirty = true;
}
// Ahead/behind — detect base branch with layered fallback (ported from statusline.sh)
const baseBranch = await detectBaseBranch(branch, cwd);
let ahead = 0;
let behind = 0;
if (baseBranch) {
const revList = await gitExec(['rev-list', '--left-right', '--count', `${baseBranch}...HEAD`], cwd);
const parts = revList.split(/\s+/);
if (parts.length === 2) {
behind = parseInt(parts[0], 10) || 0;
ahead = parseInt(parts[1], 10) || 0;
}
}
// Diff stats against base
let filesChanged = 0;
let additions = 0;
let deletions = 0;
if (baseBranch) {
const diffStat = await gitExec(['diff', '--shortstat', baseBranch], cwd);
const filesMatch = diffStat.match(/(\d+)\s+file/);
const addMatch = diffStat.match(/(\d+)\s+insertion/);
const delMatch = diffStat.match(/(\d+)\s+deletion/);
filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : 0;
additions = addMatch ? parseInt(addMatch[1], 10) : 0;
deletions = delMatch ? parseInt(delMatch[1], 10) : 0;
}
// Tag and worktree info (parallel)
const [tagOutput, worktreeOutput] = await Promise.all([
gitExec(['describe', '--tags', '--abbrev=0'], cwd),
gitExec(['worktree', 'list'], cwd),
]);
const lastTag = tagOutput || null;
let commitsSinceTag = 0;
if (lastTag) {
const countOutput = await gitExec(['rev-list', `${lastTag}..HEAD`, '--count'], cwd);
commitsSinceTag = parseInt(countOutput, 10) || 0;
}
const worktreeCount = worktreeOutput
? worktreeOutput.split('\n').filter(l => l.trim().length > 0).length
: 1;
return {
branch,
dirty,
staged,
ahead,
behind,
filesChanged,
additions,
deletions,
lastTag,
commitsSinceTag,
worktreeCount,
};
}
/**
* Detect the base branch for ahead/behind calculations.
* Uses a 4-layer fallback (ported from statusline.sh):
* 1. Branch reflog ("Created from")
* 2. HEAD reflog ("checkout: moving from X to branch")
* 3. GitHub PR base branch (gh pr view, cached)
* 4. main/master fallback
*/
async function detectBaseBranch(branch, cwd) {
// Layer 1: branch reflog — look for "branch: Created from"
const branchLog = await gitExec(['reflog', 'show', branch, '--format=%gs', '-n', '1'], cwd);
const createdMatch = branchLog.match(/branch: Created from (.+)/);
if (createdMatch) {
const candidate = createdMatch[1];
if (candidate !== 'HEAD' && !candidate.includes('~')) {
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
}
// Layer 2: HEAD reflog — look for "checkout: moving from X to branch"
const headLog = await gitExec(['reflog', 'show', 'HEAD', '--format=%gs'], cwd);
const escapedBranch = branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const checkoutPattern = new RegExp(`checkout: moving from (\\S+) to ${escapedBranch}`);
for (const line of headLog.split('\n')) {
const match = line.match(checkoutPattern);
if (match) {
const candidate = match[1];
// Skip raw commit hashes and the current branch
if (candidate === branch || /^[0-9a-f]{7,}$/.test(candidate))
continue;
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
}
// Layer 3: GitHub PR base branch via gh CLI
const prBase = await shellExec('gh', ['pr', 'view', '--json', 'baseRefName', '-q', '.baseRefName'], cwd);
if (prBase) {
const exists = await gitExec(['rev-parse', '--verify', prBase], cwd);
if (exists)
return prBase;
}
// Layer 4: main/master fallback (skip if already on that branch — use remote tracking instead)
for (const candidate of ['main', 'master']) {
if (candidate === branch) {
// On main/master itself — compare against remote tracking branch
const remote = await gitExec(['rev-parse', '--verify', `origin/${candidate}`], cwd);
if (remote)
return `origin/${candidate}`;
continue;
}
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
return null;
}
//# sourceMappingURL=git.js.map
export {};
//# sourceMappingURL=index.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { readStdin } from './stdin.js';
import { loadConfig, resolveComponents } from './config.js';
import { gatherGitStatus } from './git.js';
import { parseTranscript } from './transcript.js';
import { fetchUsageData } from './usage-api.js';
import { gatherConfigCounts } from './components/config-counts.js';
import { render } from './render.js';
const OVERALL_TIMEOUT = 2000; // 2 second overall timeout
async function main() {
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT));
try {
const result = await Promise.race([run(), timeoutPromise]);
process.stdout.write(result);
}
catch {
// Timeout or error — output nothing (graceful degradation)
}
}
async function run() {
const stdin = await readStdin();
// Debug: dump raw stdin to file when DEVFLOW_HUD_DEBUG is set
if (process.env.DEVFLOW_HUD_DEBUG) {
fs.writeFileSync(process.env.DEVFLOW_HUD_DEBUG, JSON.stringify(stdin, null, 2));
}
const config = loadConfig();
const resolved = resolveComponents(config);
const components = new Set(resolved);
const cwd = stdin.cwd || process.cwd();
const devflowDir = process.env.DEVFLOW_DIR ||
path.join(process.env.HOME || homedir(), '.devflow');
// Determine what data to gather based on enabled components
const needsGit = components.has('gitBranch') ||
components.has('gitAheadBehind') ||
components.has('diffStats') ||
components.has('releaseInfo') ||
components.has('worktreeCount');
const needsTranscript = components.has('todoProgress') ||
components.has('configCounts');
const needsUsage = components.has('usageQuota');
const needsConfigCounts = components.has('configCounts');
// Parallel data gathering — only fetch what's needed
const [git, transcript, usage] = await Promise.all([
needsGit ? gatherGitStatus(cwd) : Promise.resolve(null),
needsTranscript && stdin.transcript_path
? parseTranscript(stdin.transcript_path)
: Promise.resolve(null),
needsUsage ? fetchUsageData() : Promise.resolve(null),
]);
// Session start time from transcript file creation time
let sessionStartTime = null;
if (stdin.transcript_path) {
try {
const stat = fs.statSync(stdin.transcript_path);
sessionStartTime = stat.birthtime.getTime();
}
catch { /* file may not exist yet */ }
}
// Config counts (fast, synchronous filesystem reads)
const configCountsData = needsConfigCounts
? gatherConfigCounts(cwd)
: null;
// Terminal width via stderr (stdout is piped to Claude Code)
const terminalWidth = process.stderr.columns || 120;
const ctx = {
stdin,
git,
transcript,
usage,
configCounts: configCountsData,
config: { ...config, components: resolved },
devflowDir,
sessionStartTime,
terminalWidth,
};
return render(ctx);
}
main();
//# sourceMappingURL=index.js.map
import type { GatherContext } from './types.js';
/**
* Render all enabled components into a multi-line HUD string.
* Components that return null are excluded. Empty lines are skipped.
*/
export declare function render(ctx: GatherContext): Promise<string>;
//# sourceMappingURL=render.d.ts.map
import { dim } from './colors.js';
import directory from './components/directory.js';
import gitBranch from './components/git-branch.js';
import gitAheadBehind from './components/git-ahead-behind.js';
import diffStats from './components/diff-stats.js';
import model from './components/model.js';
import contextUsage from './components/context-usage.js';
import versionBadge from './components/version-badge.js';
import sessionDuration from './components/session-duration.js';
import usageQuota from './components/usage-quota.js';
import todoProgress from './components/todo-progress.js';
import configCounts from './components/config-counts.js';
import sessionCost from './components/session-cost.js';
import releaseInfo from './components/release-info.js';
import worktreeCount from './components/worktree-count.js';
const COMPONENT_MAP = {
directory,
gitBranch,
gitAheadBehind,
diffStats,
model,
contextUsage,
versionBadge,
sessionDuration,
usageQuota,
todoProgress,
configCounts,
sessionCost,
releaseInfo,
worktreeCount,
};
/**
* Line groupings for smart layout.
* Components are assigned to lines and only rendered if enabled.
* null entries denote section breaks (blank line between sections).
*/
const LINE_GROUPS = [
// Section 1: Info (3 lines)
['directory', 'gitBranch', 'gitAheadBehind', 'releaseInfo', 'worktreeCount', 'diffStats'],
['contextUsage', 'usageQuota'],
['model', 'sessionDuration', 'sessionCost', 'configCounts'],
// --- section break ---
null,
// Section 2: Activity
['todoProgress'],
['versionBadge'],
];
const SEPARATOR = dim(' \u00B7 ');
/**
* Render all enabled components into a multi-line HUD string.
* Components that return null are excluded. Empty lines are skipped.
*/
export async function render(ctx) {
const enabled = new Set(ctx.config.components);
// Render all enabled components in parallel
const results = new Map();
const promises = [];
for (const id of enabled) {
const fn = COMPONENT_MAP[id];
if (!fn)
continue;
promises.push(fn(ctx)
.then((result) => {
if (result)
results.set(id, result);
})
.catch(() => {
/* Component failure is non-fatal */
}));
}
await Promise.all(promises);
// Assemble lines using smart layout with section breaks
const lines = [];
let pendingBreak = false;
for (const entry of LINE_GROUPS) {
if (entry === null) {
if (lines.length > 0)
pendingBreak = true;
continue;
}
const lineResults = entry
.filter((id) => enabled.has(id) && results.has(id))
.map((id) => results.get(id));
if (lineResults.length > 0) {
if (pendingBreak) {
lines.push('');
pendingBreak = false;
}
// Separate multi-line results (containing newlines) from single-line
const singleLine = [];
for (const r of lineResults) {
if (r.text.includes('\n')) {
// Flush any accumulated single-line parts first
if (singleLine.length > 0) {
lines.push(singleLine.join(SEPARATOR));
singleLine.length = 0;
}
lines.push(r.text);
}
else {
singleLine.push(r.text);
}
}
if (singleLine.length > 0) {
lines.push(singleLine.join(SEPARATOR));
}
}
}
return lines.join('\n');
}
//# sourceMappingURL=render.js.map
import type { StdinData } from './types.js';
/**
* Read and parse JSON from stdin. Returns empty object on parse failure.
*/
export declare function readStdin(): Promise<StdinData>;
//# sourceMappingURL=stdin.d.ts.map
/**
* Read and parse JSON from stdin. Returns empty object on parse failure.
*/
export function readStdin() {
return new Promise((resolve) => {
let data = '';
process.stdin.setEncoding('utf-8');
process.stdin.on('data', (chunk) => {
data += chunk;
});
process.stdin.on('end', () => {
try {
resolve(JSON.parse(data));
}
catch {
resolve({});
}
});
process.stdin.on('error', () => {
resolve({});
});
process.stdin.resume();
});
}
//# sourceMappingURL=stdin.js.map
import type { TranscriptData } from './types.js';
/**
* Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress.
* Returns null if the file doesn't exist or can't be parsed.
*/
export declare function parseTranscript(transcriptPath: string): Promise<TranscriptData | null>;
//# sourceMappingURL=transcript.d.ts.map
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as readline from 'node:readline';
// Precompiled patterns
const TODO_WRITE_NAME = /^TodoWrite$/i;
/**
* Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress.
* Returns null if the file doesn't exist or can't be parsed.
*/
export async function parseTranscript(transcriptPath) {
try {
if (!fs.existsSync(transcriptPath))
return null;
const tools = new Map();
const agents = new Map();
const skills = new Set();
let todosCompleted = 0;
let todosTotal = 0;
const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' });
const rl = readline.createInterface({
input: stream,
crlfDelay: Infinity,
});
for await (const line of rl) {
if (!line.trim())
continue;
try {
const entry = JSON.parse(line);
// Turn boundary: clear tools and agents on each human message
// so only the current turn's activity shows in the HUD
if (entry.type === 'human') {
tools.clear();
agents.clear();
continue;
}
const todoResult = processEntry(entry, tools, agents, skills);
if (todoResult) {
todosCompleted = todoResult.completed;
todosTotal = todoResult.total;
}
}
catch {
// Skip malformed lines
}
}
return {
tools: Array.from(tools.values()),
agents: Array.from(agents.values()),
todos: { completed: todosCompleted, total: todosTotal },
skills: Array.from(skills),
};
}
catch {
return null;
}
}
function processEntry(entry, tools, agents, skills) {
if (entry.type !== 'assistant' || !entry.message)
return null;
const message = entry.message;
if (!Array.isArray(message.content))
return null;
let todoResult = null;
for (const block of message.content) {
const blockType = block.type;
if (blockType === 'tool_use') {
const name = block.name;
const id = block.id;
if (name === 'Agent' || name === 'Task') {
// Agent spawn
const input = block.input;
const agentType = input?.subagent_type || 'Agent';
const agentDesc = typeof input?.description === 'string' ? input.description : undefined;
agents.set(id, {
name: agentType,
model: input?.model,
status: 'running',
description: agentDesc,
});
}
else if (name === 'Skill') {
// Track loaded skills
const input = block.input;
if (input?.skill && typeof input.skill === 'string') {
skills.add(input.skill);
}
}
else if (TODO_WRITE_NAME.test(name)) {
// Track todos
const input = block.input;
const todos = input?.todos;
if (Array.isArray(todos)) {
const total = todos.length;
const completed = todos.filter((t) => t.status === 'completed').length;
todoResult = { completed, total };
}
}
else {
// Regular tool — extract file target for Read/Edit/Write
const input = block.input;
let target;
if (input?.file_path && typeof input.file_path === 'string') {
target = path.basename(input.file_path);
}
// Extract description: prefer input.description, fallback to first 4 words of command (Bash)
let description;
if (typeof input?.description === 'string') {
description = input.description;
}
else if (name === 'Bash' && typeof input?.command === 'string') {
description = input.command.split(/\s+/).slice(0, 4).join(' ');
}
tools.set(id, { name, status: 'running', target, description });
}
}
else if (blockType === 'tool_result') {
const toolUseId = block.tool_use_id;
const toolEntry = tools.get(toolUseId);
if (toolEntry) {
toolEntry.status = 'completed';
}
const agentEntry = agents.get(toolUseId);
if (agentEntry) {
agentEntry.status = 'completed';
}
}
}
return todoResult;
}
//# sourceMappingURL=transcript.js.map
/**
* StdinData — the JSON that Claude Code pipes to statusLine commands.
*/
export interface StdinData {
model?: {
display_name?: string;
id?: string;
};
cwd?: string;
context_window?: {
context_window_size?: number;
current_usage?: {
input_tokens?: number;
output_tokens?: number;
};
used_percentage?: number;
};
cost?: {
total_cost_usd?: number;
};
session_id?: string;
transcript_path?: string;
}
/**
* Component IDs — the 14 HUD components.
*/
export type ComponentId = 'directory' | 'gitBranch' | 'gitAheadBehind' | 'diffStats' | 'model' | 'contextUsage' | 'versionBadge' | 'sessionDuration' | 'usageQuota' | 'todoProgress' | 'configCounts' | 'sessionCost' | 'releaseInfo' | 'worktreeCount';
/**
* HUD config persisted to ~/.devflow/hud.json.
*/
export interface HudConfig {
enabled: boolean;
detail: boolean;
}
/**
* Component render result.
*/
export interface ComponentResult {
text: string;
raw: string;
}
/**
* Component function signature.
*/
export type ComponentFn = (ctx: GatherContext) => Promise<ComponentResult | null>;
/**
* Git status data gathered from the working directory.
*/
export interface GitStatus {
branch: string;
dirty: boolean;
staged: boolean;
ahead: number;
behind: number;
filesChanged: number;
additions: number;
deletions: number;
lastTag: string | null;
commitsSinceTag: number;
worktreeCount: number;
}
/**
* Transcript data parsed from session JSONL.
*/
export interface TranscriptData {
tools: Array<{
name: string;
status: 'running' | 'completed';
target?: string;
description?: string;
}>;
agents: Array<{
name: string;
model?: string;
status: 'running' | 'completed';
description?: string;
}>;
todos: {
completed: number;
total: number;
};
skills: string[];
}
/**
* Usage API data.
*/
export interface UsageData {
fiveHourPercent: number | null;
sevenDayPercent: number | null;
}
/**
* Config counts data for the configCounts component.
*/
export interface ConfigCountsData {
claudeMdFiles: number;
rules: number;
mcpServers: number;
hooks: number;
}
/**
* Gather context passed to all component render functions.
*/
export interface GatherContext {
stdin: StdinData;
git: GitStatus | null;
transcript: TranscriptData | null;
usage: UsageData | null;
configCounts: ConfigCountsData | null;
config: HudConfig & {
components: ComponentId[];
};
devflowDir: string;
sessionStartTime: number | null;
terminalWidth: number;
}
//# sourceMappingURL=types.d.ts.map
export {};
//# sourceMappingURL=types.js.map
import type { UsageData } from './types.js';
/**
* Fetch usage quota data from the Anthropic API.
* Uses caching with backoff for rate limiting. Returns null on failure.
*/
export declare function fetchUsageData(): Promise<UsageData | null>;
//# sourceMappingURL=usage-api.d.ts.map
import * as fs from 'node:fs';
import { readCache, writeCache, readCacheStale } from './cache.js';
import { getCredentials } from './credentials.js';
const USAGE_CACHE_KEY = 'usage';
const USAGE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const USAGE_FAIL_TTL = 15 * 1000; // 15 seconds
const API_TIMEOUT = 1_500; // Must fit within 2s overall HUD timeout
const BACKOFF_CACHE_KEY = 'usage-backoff';
const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG;
function debugLog(msg, data) {
if (!DEBUG)
return;
const entry = { ts: new Date().toISOString(), source: 'usage-api', msg, ...data };
fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n');
}
/**
* Fetch usage quota data from the Anthropic API.
* Uses caching with backoff for rate limiting. Returns null on failure.
*/
export async function fetchUsageData() {
// Check backoff
const backoff = readCache(BACKOFF_CACHE_KEY);
if (backoff && Date.now() < backoff.retryAfter) {
debugLog('skipped: backoff active', { retryAfter: backoff.retryAfter });
return readCacheStale(USAGE_CACHE_KEY);
}
// Check cache
const cached = readCache(USAGE_CACHE_KEY);
if (cached)
return cached;
const creds = await getCredentials();
if (!creds) {
debugLog('no OAuth credentials found');
return null;
}
const token = creds.accessToken;
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), API_TIMEOUT);
debugLog('fetching usage', { timeout: API_TIMEOUT });
const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'anthropic-beta': 'oauth-2025-04-20',
},
signal: controller.signal,
});
clearTimeout(timeout);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
const delay = Math.min(retryAfter * 1000, 5 * 60 * 1000);
writeCache(BACKOFF_CACHE_KEY, { retryAfter: Date.now() + delay, delay }, delay);
debugLog('rate limited (429)', { retryAfter, delay });
return readCacheStale(USAGE_CACHE_KEY);
}
if (!response.ok) {
debugLog('non-200 response', { status: response.status, statusText: response.statusText });
writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL);
return readCacheStale(USAGE_CACHE_KEY);
}
const body = (await response.json());
const fiveHour = body.five_hour;
const sevenDay = body.seven_day;
const data = {
fiveHourPercent: typeof fiveHour?.utilization === 'number'
? Math.round(Math.max(0, Math.min(100, fiveHour.utilization)))
: null,
sevenDayPercent: typeof sevenDay?.utilization === 'number'
? Math.round(Math.max(0, Math.min(100, sevenDay.utilization)))
: null,
};
debugLog('usage fetched', { fiveHour: data.fiveHourPercent, sevenDay: data.sevenDayPercent });
writeCache(USAGE_CACHE_KEY, data, USAGE_CACHE_TTL);
return data;
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
debugLog('fetch failed', { error: message });
writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL);
return readCacheStale(USAGE_CACHE_KEY);
}
}
//# sourceMappingURL=usage-api.js.map
# Stub Detection Patterns
Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues.
Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns.
---
## 1. Component Stubs
Render nothing meaningful or return placeholder markup.
```tsx
// STUB — returns static text, no real rendering
function UserProfile({ userId }: Props) {
return <div>Name</div>;
}
// REAL — fetches and renders actual data
function UserProfile({ userId }: Props) {
const user = useUser(userId);
if (!user) return <Skeleton />;
return <div><h2>{user.name}</h2><p>{user.email}</p></div>;
}
```
**Patterns to flag:**
- `return null` / `return <></>` in components that should render content
- Empty function bodies (`{}`) for handlers or lifecycle methods
- Components returning only hardcoded strings with no data binding
---
## 2. API / Service Stubs
Functions that exist in signature but throw, return hardcoded values, or do nothing.
```typescript
// STUB — throws instead of implementing
async function createOrder(items: CartItem[]): Promise<Order> {
throw new Error("TODO: implement");
}
// STUB — hardcoded return, no real logic
async function getUser(id: string): Promise<User> {
return { id, name: "Test User", email: "test@test.com" };
}
// REAL — actual implementation
async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> {
const validated = validateItems(items);
if (!validated.ok) return validated;
const order = await db.orders.create({ items: validated.value });
await queue.publish("order.created", order);
return { ok: true, value: order };
}
```
**Patterns to flag:**
- `throw new Error("TODO")` / `throw new Error("Not implemented")`
- Functions returning hardcoded objects (no DB/API/computation)
- `"Not implemented"` strings in response bodies
- Empty async functions (`async function foo() {}`)
---
## 3. Hook / Effect Stubs
State and effects declared but not wired to behavior.
```typescript
// STUB — effect does nothing
useEffect(() => {}, [userId]);
// STUB — state declared, setter never called
const [items, setItems] = useState<Item[]>([]);
// ... setItems never appears in the component
// STUB — custom hook returns static value
function usePermissions(): Permissions {
return { canEdit: true, canDelete: false };
}
// REAL — custom hook with actual logic
function usePermissions(): Permissions {
const { user } = useAuth();
const { data } = useQuery(["permissions", user.role], fetchPermissions);
return data ?? DEFAULT_PERMISSIONS;
}
```
**Patterns to flag:**
- `useEffect(() => {}, [...])` — empty effect body
- `useState` where the setter is never called in the component
- Custom hooks returning static/hardcoded values
- `useMemo`/`useCallback` wrapping static values
---
## 4. Wiring Gaps
Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end.
```typescript
// GAP — fetch without await (result discarded)
function loadDashboard() {
fetchMetrics(); // Promise floats, never awaited
return <Dashboard />;
}
// GAP — state declared but never rendered
const [error, setError] = useState<string | null>(null);
// ... error never appears in JSX
// GAP — handler defined but not bound
function handleSubmit(data: FormData) { /* real logic */ }
// ... <form onSubmit={handleSubmit}> never appears
// GAP — route defined with no-op handler
app.post("/api/orders", (_req, res) => {
res.status(200).json({ ok: true });
});
// GAP — env var read but unused
const API_KEY = process.env.STRIPE_API_KEY;
// ... API_KEY never passed to any client or fetch call
```
**Patterns to flag:**
- `fetch`/`axios`/API call without `await` or `.then` (result discarded)
- State variable (`useState`, `useRef`) never rendered or read in output
- Event handler defined but not bound to any element/listener
- Route/endpoint registered with empty or no-op handler
- Environment variable or config read but never used downstream
- Import used only in type position but imported as value (in non-type-only import)
# Stub Detection Patterns
Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues.
Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns.
---
## 1. Component Stubs
Render nothing meaningful or return placeholder markup.
```tsx
// STUB — returns static text, no real rendering
function UserProfile({ userId }: Props) {
return <div>Name</div>;
}
// REAL — fetches and renders actual data
function UserProfile({ userId }: Props) {
const user = useUser(userId);
if (!user) return <Skeleton />;
return <div><h2>{user.name}</h2><p>{user.email}</p></div>;
}
```
**Patterns to flag:**
- `return null` / `return <></>` in components that should render content
- Empty function bodies (`{}`) for handlers or lifecycle methods
- Components returning only hardcoded strings with no data binding
---
## 2. API / Service Stubs
Functions that exist in signature but throw, return hardcoded values, or do nothing.
```typescript
// STUB — throws instead of implementing
async function createOrder(items: CartItem[]): Promise<Order> {
throw new Error("TODO: implement");
}
// STUB — hardcoded return, no real logic
async function getUser(id: string): Promise<User> {
return { id, name: "Test User", email: "test@test.com" };
}
// REAL — actual implementation
async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> {
const validated = validateItems(items);
if (!validated.ok) return validated;
const order = await db.orders.create({ items: validated.value });
await queue.publish("order.created", order);
return { ok: true, value: order };
}
```
**Patterns to flag:**
- `throw new Error("TODO")` / `throw new Error("Not implemented")`
- Functions returning hardcoded objects (no DB/API/computation)
- `"Not implemented"` strings in response bodies
- Empty async functions (`async function foo() {}`)
---
## 3. Hook / Effect Stubs
State and effects declared but not wired to behavior.
```typescript
// STUB — effect does nothing
useEffect(() => {}, [userId]);
// STUB — state declared, setter never called
const [items, setItems] = useState<Item[]>([]);
// ... setItems never appears in the component
// STUB — custom hook returns static value
function usePermissions(): Permissions {
return { canEdit: true, canDelete: false };
}
// REAL — custom hook with actual logic
function usePermissions(): Permissions {
const { user } = useAuth();
const { data } = useQuery(["permissions", user.role], fetchPermissions);
return data ?? DEFAULT_PERMISSIONS;
}
```
**Patterns to flag:**
- `useEffect(() => {}, [...])` — empty effect body
- `useState` where the setter is never called in the component
- Custom hooks returning static/hardcoded values
- `useMemo`/`useCallback` wrapping static values
---
## 4. Wiring Gaps
Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end.
```typescript
// GAP — fetch without await (result discarded)
function loadDashboard() {
fetchMetrics(); // Promise floats, never awaited
return <Dashboard />;
}
// GAP — state declared but never rendered
const [error, setError] = useState<string | null>(null);
// ... error never appears in JSX
// GAP — handler defined but not bound
function handleSubmit(data: FormData) { /* real logic */ }
// ... <form onSubmit={handleSubmit}> never appears
// GAP — route defined with no-op handler
app.post("/api/orders", (_req, res) => {
res.status(200).json({ ok: true });
});
// GAP — env var read but unused
const API_KEY = process.env.STRIPE_API_KEY;
// ... API_KEY never passed to any client or fetch call
```
**Patterns to flag:**
- `fetch`/`axios`/API call without `await` or `.then` (result discarded)
- State variable (`useState`, `useRef`) never rendered or read in output
- Event handler defined but not bound to any element/listener
- Route/endpoint registered with empty or no-op handler
- Environment variable or config read but never used downstream
- Import used only in type position but imported as value (in non-type-only import)
#!/usr/bin/env bash
# DevFlow HUD — configurable TypeScript status line
# Receives JSON via stdin from Claude Code, outputs ANSI-formatted HUD
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
exec node "${SCRIPT_DIR}/hud/index.js"
export declare function getCacheDir(): string;
/**
* Read a cached value. Returns null if missing or expired.
*/
export declare function readCache<T>(key: string): T | null;
/**
* Read a cached value regardless of TTL (stale data). Returns null if missing.
*/
export declare function readCacheStale<T>(key: string): T | null;
/**
* Write a value to cache with a TTL in milliseconds.
*/
export declare function writeCache<T>(key: string, data: T, ttlMs: number): void;
//# sourceMappingURL=cache.d.ts.map
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cli/hud/cache.ts"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAoBD;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAElD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEvD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAWvE"}
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
export function getCacheDir() {
const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow');
return path.join(devflowDir, 'cache');
}
/**
* Read a cached value. Returns null if missing or expired.
* When `ignoreExpiry` is true, returns data regardless of TTL (stale read).
*/
function readCacheEntry(key, ignoreExpiry) {
try {
const filePath = path.join(getCacheDir(), `${key}.json`);
const raw = fs.readFileSync(filePath, 'utf-8');
const entry = JSON.parse(raw);
if (ignoreExpiry || Date.now() - entry.timestamp < entry.ttl) {
return entry.data;
}
return null;
}
catch {
return null;
}
}
/**
* Read a cached value. Returns null if missing or expired.
*/
export function readCache(key) {
return readCacheEntry(key, false);
}
/**
* Read a cached value regardless of TTL (stale data). Returns null if missing.
*/
export function readCacheStale(key) {
return readCacheEntry(key, true);
}
/**
* Write a value to cache with a TTL in milliseconds.
*/
export function writeCache(key, data, ttlMs) {
try {
const dir = getCacheDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const entry = { data, timestamp: Date.now(), ttl: ttlMs };
fs.writeFileSync(path.join(dir, `${key}.json`), JSON.stringify(entry));
}
catch {
// Cache write failure is non-fatal
}
}
//# sourceMappingURL=cache.js.map
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cli/hud/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAQlC,MAAM,UAAU,WAAW;IACzB,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAI,GAAW,EAAE,YAAqB;IAC3D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QACzD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;QAC/C,IAAI,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;YAC7D,OAAO,KAAK,CAAC,IAAI,CAAC;QACpB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAI,GAAW;IACtC,OAAO,cAAc,CAAI,GAAG,EAAE,KAAK,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAI,GAAW;IAC3C,OAAO,cAAc,CAAI,GAAG,EAAE,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAI,GAAW,EAAE,IAAO,EAAE,KAAa;IAC/D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC;QACD,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;QACzE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC"}
/**
* ANSI color helpers — no dependencies, precompiled escape sequences.
* Used by HUD components for direct terminal output (not @clack/prompts).
*/
export declare function bold(s: string): string;
export declare function dim(s: string): string;
export declare function red(s: string): string;
export declare function green(s: string): string;
export declare function yellow(s: string): string;
export declare function blue(s: string): string;
export declare function magenta(s: string): string;
export declare function cyan(s: string): string;
export declare function gray(s: string): string;
export declare function white(s: string): string;
export declare function orange(s: string): string;
export declare function brightRed(s: string): string;
export declare function boldRed(s: string): string;
export declare function bgGreen(s: string): string;
export declare function bgYellow(s: string): string;
export declare function bgRed(s: string): string;
export declare function truncate(s: string, max: number): string;
export declare function stripAnsi(s: string): string;
//# sourceMappingURL=colors.d.ts.map
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/cli/hud/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErC;AACD,wBAAgB,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAErC;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AACD,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AACD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AACD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AACD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEvD;AAID,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C"}
/**
* ANSI color helpers — no dependencies, precompiled escape sequences.
* Used by HUD components for direct terminal output (not @clack/prompts).
*/
const ESC = '\x1b[';
const RESET = `${ESC}0m`;
export function bold(s) {
return `${ESC}1m${s}${RESET}`;
}
export function dim(s) {
return `${ESC}2m${s}${RESET}`;
}
export function red(s) {
return `${ESC}31m${s}${RESET}`;
}
export function green(s) {
return `${ESC}32m${s}${RESET}`;
}
export function yellow(s) {
return `${ESC}33m${s}${RESET}`;
}
export function blue(s) {
return `${ESC}34m${s}${RESET}`;
}
export function magenta(s) {
return `${ESC}35m${s}${RESET}`;
}
export function cyan(s) {
return `${ESC}36m${s}${RESET}`;
}
export function gray(s) {
return `${ESC}90m${s}${RESET}`;
}
export function white(s) {
return `${ESC}37m${s}${RESET}`;
}
export function orange(s) {
return `${ESC}38;5;208m${s}${RESET}`;
}
export function brightRed(s) {
return `${ESC}91m${s}${RESET}`;
}
export function boldRed(s) {
return `${ESC}1;31m${s}${RESET}`;
}
export function bgGreen(s) {
return `${ESC}42m${s}${RESET}`;
}
export function bgYellow(s) {
return `${ESC}43m${s}${RESET}`;
}
export function bgRed(s) {
return `${ESC}41m${s}${RESET}`;
}
export function truncate(s, max) {
return s.length > max ? s.slice(0, max - 1) + '\u2026' : s;
}
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
export function stripAnsi(s) {
return s.replace(ANSI_PATTERN, '');
}
//# sourceMappingURL=colors.js.map
{"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/cli/hud/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC;AAEzB,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AAChC,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,GAAG,GAAG,KAAK,CAAC,GAAG,KAAK,EAAE,CAAC;AAChC,CAAC;AACD,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,GAAG,GAAG,YAAY,CAAC,GAAG,KAAK,EAAE,CAAC;AACvC,CAAC;AACD,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,QAAQ,CAAC,GAAG,KAAK,EAAE,CAAC;AACnC,CAAC;AACD,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,OAAO,GAAG,GAAG,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,CAAS,EAAE,GAAW;IAC7C,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,MAAM,YAAY,GAAG,iBAAiB,CAAC;AAEvC,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC"}
import type { ComponentResult, GatherContext, ConfigCountsData } from '../types.js';
/**
* Gather configuration counts for the configCounts component.
* Exported for use by the main HUD entry point.
*/
export declare function gatherConfigCounts(cwd: string): ConfigCountsData;
export default function configCounts(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=config-counts.d.ts.map
{"version":3,"file":"config-counts.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/config-counts.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAqCpF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAoChE;AAED,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAejC"}
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { dim } from '../colors.js';
function countClaudeMdFiles(cwd) {
let count = 0;
// Check project CLAUDE.md
if (fs.existsSync(path.join(cwd, 'CLAUDE.md')))
count++;
// Check user CLAUDE.md
const claudeDir = process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude');
if (fs.existsSync(path.join(claudeDir, 'CLAUDE.md')))
count++;
return count;
}
function countFromSettings(settingsPath) {
try {
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
const mcpServers = settings.mcpServers
? Object.keys(settings.mcpServers).length
: 0;
let hooks = 0;
if (settings.hooks) {
const hooksObj = settings.hooks;
for (const event of Object.values(hooksObj)) {
if (Array.isArray(event))
hooks += event.length;
}
}
return { mcpServers, hooks };
}
catch {
return { mcpServers: 0, hooks: 0 };
}
}
/**
* Gather configuration counts for the configCounts component.
* Exported for use by the main HUD entry point.
*/
export function gatherConfigCounts(cwd) {
const claudeDir = process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude');
const claudeMdFiles = countClaudeMdFiles(cwd);
// Count rules (.md/.mdc files in .claude/rules)
let rules = 0;
for (const rulesDir of [
path.join(cwd, '.claude', 'rules'),
path.join(claudeDir, 'rules'),
]) {
try {
const files = fs.readdirSync(rulesDir);
rules += files.filter((f) => f.endsWith('.md') || f.endsWith('.mdc')).length;
}
catch {
/* ignore */
}
}
// Aggregate settings from user and project
const userSettings = countFromSettings(path.join(claudeDir, 'settings.json'));
const projectSettings = countFromSettings(path.join(cwd, '.claude', 'settings.json'));
return {
claudeMdFiles,
rules,
mcpServers: userSettings.mcpServers + projectSettings.mcpServers,
hooks: userSettings.hooks + projectSettings.hooks,
};
}
export default async function configCounts(ctx) {
if (!ctx.configCounts)
return null;
const { claudeMdFiles, rules: ruleCount, mcpServers, hooks: hookCount } = ctx.configCounts;
const skillCount = ctx.transcript?.skills.length ?? 0;
const parts = [];
if (claudeMdFiles > 0)
parts.push(`${claudeMdFiles} CLAUDE.md`);
if (ruleCount > 0)
parts.push(`${ruleCount} rules`);
if (mcpServers > 0)
parts.push(`${mcpServers} MCPs`);
if (hookCount > 0)
parts.push(`${hookCount} hooks`);
if (skillCount > 0)
parts.push(`${skillCount} skills`);
if (parts.length === 0)
return null;
const raw = parts.join(' \u00B7 ');
const text = parts.map((p) => dim(p)).join(dim(' \u00B7 '));
return { text, raw };
}
//# sourceMappingURL=config-counts.js.map
{"version":3,"file":"config-counts.js","sourceRoot":"","sources":["../../../src/cli/hud/components/config-counts.ts"],"names":[],"mappings":"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"}
import type { ComponentResult, GatherContext } from '../types.js';
/**
* Context window usage component.
* Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red.
* At >85% appends token breakdown: (in: Nk).
*/
export default function contextUsage(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=context-usage.d.ts.map
{"version":3,"file":"context-usage.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/context-usage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKlE;;;;GAIG;AACH,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4CjC"}
import { dim, green, yellow, red } from '../colors.js';
const BAR_WIDTH = 8;
/**
* Context window usage component.
* Visual bar with 3-tier color gradient matching usage-quota: green / yellow / red.
* At >85% appends token breakdown: (in: Nk).
*/
export default async function contextUsage(ctx) {
const cw = ctx.stdin.context_window;
if (!cw)
return null;
let pct = null;
if (cw.used_percentage !== undefined) {
pct = Math.round(cw.used_percentage);
}
else if (cw.context_window_size && cw.current_usage?.input_tokens !== undefined) {
pct = Math.round((cw.current_usage.input_tokens / cw.context_window_size) * 100);
}
if (pct === null)
return null;
const filled = Math.round((pct / 100) * BAR_WIDTH);
const empty = BAR_WIDTH - filled;
let colorFn;
if (pct < 50) {
colorFn = green;
}
else if (pct < 80) {
colorFn = yellow;
}
else {
colorFn = red;
}
const filledBar = '\u2588'.repeat(filled);
const emptyBar = '\u2591'.repeat(empty);
let suffix = '';
if (pct > 85 && cw.current_usage?.input_tokens !== undefined) {
const inK = Math.round(cw.current_usage.input_tokens / 1000);
suffix = ` (in: ${inK}k)`;
}
const raw = `Current Session ${filledBar}${emptyBar} ${pct}%${suffix}`;
const text = dim('Current Session ') +
colorFn(filledBar) +
dim(emptyBar) +
' ' +
colorFn(`${pct}%`) +
(suffix ? dim(suffix) : '');
return { text, raw };
}
//# sourceMappingURL=context-usage.js.map
{"version":3,"file":"context-usage.js","sourceRoot":"","sources":["../../../src/cli/hud/components/context-usage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;;;;GAIG;AACH,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,YAAY,CACxC,GAAkB;IAElB,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC;IACpC,IAAI,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAErB,IAAI,GAAG,GAAkB,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,eAAe,KAAK,SAAS,EAAE,CAAC;QACrC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,EAAE,CAAC,mBAAmB,IAAI,EAAE,CAAC,aAAa,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;QAClF,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,YAAY,GAAG,EAAE,CAAC,mBAAmB,CAAC,GAAG,GAAG,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAEjC,IAAI,OAA8B,CAAC;IACnC,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;QACb,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;SAAM,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;QACpB,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC,aAAa,EAAE,YAAY,KAAK,SAAS,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;QAC7D,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IAC5B,CAAC;IAED,MAAM,GAAG,GAAG,mBAAmB,SAAS,GAAG,QAAQ,IAAI,GAAG,IAAI,MAAM,EAAE,CAAC;IACvE,MAAM,IAAI,GACR,GAAG,CAAC,kBAAkB,CAAC;QACvB,OAAO,CAAC,SAAS,CAAC;QAClB,GAAG,CAAC,QAAQ,CAAC;QACb,GAAG;QACH,OAAO,CAAC,GAAG,GAAG,GAAG,CAAC;QAClB,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE9B,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function diffStats(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=diff-stats.d.ts.map
{"version":3,"file":"diff-stats.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/diff-stats.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA+BjC"}
import { dim, green, red } from '../colors.js';
export default async function diffStats(ctx) {
if (!ctx.git)
return null;
const { filesChanged, additions, deletions } = ctx.git;
if (filesChanged === 0 && additions === 0 && deletions === 0)
return null;
const filePart = filesChanged > 0
? `${filesChanged} file${filesChanged === 1 ? '' : 's'}`
: '';
const lineParts = [];
if (additions > 0)
lineParts.push(`+${additions}`);
if (deletions > 0)
lineParts.push(`-${deletions}`);
const sections = [];
const rawSections = [];
if (filePart) {
sections.push(dim(filePart));
rawSections.push(filePart);
}
if (lineParts.length > 0) {
const lineText = lineParts
.map((p) => (p.startsWith('+') ? green(p) : red(p)))
.join(' ');
sections.push(lineText);
rawSections.push(lineParts.join(' '));
}
if (sections.length === 0)
return null;
return {
text: sections.join(dim(' \u00B7 ')),
raw: rawSections.join(' \u00B7 '),
};
}
//# sourceMappingURL=diff-stats.js.map
{"version":3,"file":"diff-stats.js","sourceRoot":"","sources":["../../../src/cli/hud/components/diff-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IACvD,IAAI,YAAY,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,MAAM,QAAQ,GAAG,YAAY,GAAG,CAAC;QAC/B,CAAC,CAAC,GAAG,YAAY,QAAQ,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QACxD,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,SAAS,GAAG,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IACnD,IAAI,SAAS,GAAG,CAAC;QAAE,SAAS,CAAC,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7B,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,SAAS;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;aACnD,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACpC,GAAG,EAAE,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC;KAClC,CAAC;AACJ,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function directory(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=directory.d.ts.map
{"version":3,"file":"directory.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/directory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"}
import { bold, white } from '../colors.js';
import * as path from 'node:path';
export default async function directory(ctx) {
const cwd = ctx.stdin.cwd;
if (!cwd)
return null;
const name = path.basename(cwd);
return { text: bold(white(name)), raw: name };
}
//# sourceMappingURL=directory.js.map
{"version":3,"file":"directory.js","sourceRoot":"","sources":["../../../src/cli/hud/components/directory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAChD,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function gitAheadBehind(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=git-ahead-behind.d.ts.map
{"version":3,"file":"git-ahead-behind.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/git-ahead-behind.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,cAAc,CAC1C,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CASjC"}
import { dim } from '../colors.js';
export default async function gitAheadBehind(ctx) {
if (!ctx.git)
return null;
const { ahead, behind } = ctx.git;
if (ahead === 0 && behind === 0)
return null;
const rawParts = [];
if (ahead > 0)
rawParts.push(`${ahead}\u2191`);
if (behind > 0)
rawParts.push(`${behind}\u2193`);
const raw = rawParts.join(' ');
return { text: dim(raw), raw };
}
//# sourceMappingURL=git-ahead-behind.js.map
{"version":3,"file":"git-ahead-behind.js","sourceRoot":"","sources":["../../../src/cli/hud/components/git-ahead-behind.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,cAAc,CAC1C,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAClC,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,KAAK,GAAG,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC;IAC/C,IAAI,MAAM,GAAG,CAAC;QAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,QAAQ,CAAC,CAAC;IACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function gitBranch(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=git-branch.d.ts.map
{"version":3,"file":"git-branch.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/git-branch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,SAAS,CACrC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAWjC"}
import { white, yellow, green } from '../colors.js';
export default async function gitBranch(ctx) {
if (!ctx.git)
return null;
const dirtyMark = ctx.git.dirty ? '*' : '';
const stagedMark = ctx.git.staged ? '+' : '';
const indicator = dirtyMark + stagedMark;
const text = white(ctx.git.branch) +
(dirtyMark ? yellow(dirtyMark) : '') +
(stagedMark ? green(stagedMark) : '');
const raw = ctx.git.branch + indicator;
return { text, raw };
}
//# sourceMappingURL=git-branch.js.map
{"version":3,"file":"git-branch.js","sourceRoot":"","sources":["../../../src/cli/hud/components/git-branch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,SAAS,CACrC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3C,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;IACzC,MAAM,IAAI,GACR,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;QACrB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,CAAC;IACvC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function model(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=model.d.ts.map
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,KAAK,CACjC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmBjC"}
import { dim, white } from '../colors.js';
export default async function model(ctx) {
const name = ctx.stdin.model?.display_name;
if (!name)
return null;
// Strip "Claude " prefix and trailing context info like "(1M context)" for brevity
const short = name
.replace(/^Claude\s+/i, '')
.replace(/\s*\(\d+[KkMm]\s*context\)\s*$/, '');
const cwSize = ctx.stdin.context_window?.context_window_size;
let sizeStr = '';
if (cwSize) {
sizeStr =
cwSize >= 1_000_000
? ` [${Math.round(cwSize / 1_000_000)}m]`
: ` [${Math.round(cwSize / 1000)}k]`;
}
const raw = short + sizeStr;
return { text: white(short) + (sizeStr ? dim(sizeStr) : ''), raw };
}
//# sourceMappingURL=model.js.map
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/cli/hud/components/model.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,KAAK,CACjC,GAAkB;IAElB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,mFAAmF;IACnF,MAAM,KAAK,GAAG,IAAI;SACf,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC;SAC1B,OAAO,CAAC,gCAAgC,EAAE,EAAE,CAAC,CAAC;IAEjD,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,mBAAmB,CAAC;IAC7D,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,MAAM,EAAE,CAAC;QACX,OAAO;YACL,MAAM,IAAI,SAAS;gBACjB,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,IAAI;gBACzC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;IAC3C,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,GAAG,OAAO,CAAC;IAC5B,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC;AACrE,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function releaseInfo(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=release-info.d.ts.map
{"version":3,"file":"release-info.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/release-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,WAAW,CACvC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"}
import { dim } from '../colors.js';
export default async function releaseInfo(ctx) {
if (!ctx.git?.lastTag)
return null;
const { lastTag, commitsSinceTag } = ctx.git;
const raw = commitsSinceTag > 0 ? `${lastTag} +${commitsSinceTag}` : lastTag;
return { text: dim(raw), raw };
}
//# sourceMappingURL=release-info.js.map
{"version":3,"file":"release-info.js","sourceRoot":"","sources":["../../../src/cli/hud/components/release-info.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACvC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC;IAC7C,MAAM,GAAG,GAAG,eAAe,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,eAAe,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC7E,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function sessionCost(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=session-cost.d.ts.map
{"version":3,"file":"session-cost.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/session-cost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,WAAW,CACvC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAKjC"}
import { dim } from '../colors.js';
export default async function sessionCost(ctx) {
const cost = ctx.stdin.cost?.total_cost_usd;
if (cost == null)
return null;
const formatted = `$${cost.toFixed(2)}`;
return { text: dim(formatted), raw: formatted };
}
//# sourceMappingURL=session-cost.js.map
{"version":3,"file":"session-cost.js","sourceRoot":"","sources":["../../../src/cli/hud/components/session-cost.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,WAAW,CACvC,GAAkB;IAElB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC;IAC5C,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;AAClD,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function sessionDuration(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=session-duration.d.ts.map
{"version":3,"file":"session-duration.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/session-duration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,eAAe,CAC3C,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAajC"}
import { dim } from '../colors.js';
export default async function sessionDuration(ctx) {
if (!ctx.sessionStartTime)
return null;
const elapsed = Math.floor((Date.now() - ctx.sessionStartTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const hours = Math.floor(minutes / 60);
let label;
if (hours > 0) {
label = `${hours}h ${minutes % 60}m`;
}
else {
label = `${minutes}m`;
}
const text = `\u23F1 ${label}`;
return { text: dim(text), raw: text };
}
//# sourceMappingURL=session-duration.js.map
{"version":3,"file":"session-duration.js","sourceRoot":"","sources":["../../../src/cli/hud/components/session-duration.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,eAAe,CAC3C,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,IAAI,KAAa,CAAC;IAClB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,KAAK,GAAG,GAAG,KAAK,KAAK,OAAO,GAAG,EAAE,GAAG,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,GAAG,OAAO,GAAG,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,KAAK,EAAE,CAAC;IAC/B,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACxC,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function todoProgress(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=todo-progress.d.ts.map
{"version":3,"file":"todo-progress.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/todo-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAMjC"}
import { dim } from '../colors.js';
export default async function todoProgress(ctx) {
if (!ctx.transcript)
return null;
const { todos } = ctx.transcript;
if (todos.total === 0)
return null;
const label = `${todos.completed}/${todos.total} todos`;
return { text: dim(label), raw: label };
}
//# sourceMappingURL=todo-progress.js.map
{"version":3,"file":"todo-progress.js","sourceRoot":"","sources":["../../../src/cli/hud/components/todo-progress.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,YAAY,CACxC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;IACjC,IAAI,KAAK,CAAC,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,QAAQ,CAAC;IACxD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function usageQuota(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=usage-quota.d.ts.map
{"version":3,"file":"usage-quota.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/usage-quota.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA6BlE,wBAA8B,UAAU,CACtC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAqBjC"}
import { green, yellow, red, dim } from '../colors.js';
const BAR_WIDTH = 8;
function renderBar(percent) {
const filled = Math.round((percent / 100) * BAR_WIDTH);
const empty = BAR_WIDTH - filled;
let colorFn;
if (percent < 50) {
colorFn = green;
}
else if (percent < 80) {
colorFn = yellow;
}
else {
colorFn = red;
}
const filledBar = '\u2588'.repeat(filled);
const emptyBar = '\u2591'.repeat(empty);
const text = colorFn(filledBar) +
dim(emptyBar) +
' ' +
colorFn(`${percent}%`);
const raw = `${filledBar}${emptyBar} ${percent}%`;
return { text, raw };
}
export default async function usageQuota(ctx) {
if (!ctx.usage)
return null;
const { fiveHourPercent, sevenDayPercent } = ctx.usage;
const parts = [];
if (fiveHourPercent !== null) {
const bar = renderBar(Math.round(fiveHourPercent));
parts.push({ text: dim('5h ') + bar.text, raw: `5h ${bar.raw}` });
}
if (sevenDayPercent !== null) {
const bar = renderBar(Math.round(sevenDayPercent));
parts.push({ text: dim('7d ') + bar.text, raw: `7d ${bar.raw}` });
}
if (parts.length === 0)
return null;
const sep = dim(' \u00B7 ');
const text = dim('Session ') + parts.map((p) => p.text).join(sep);
const raw = 'Session ' + parts.map((p) => p.raw).join(' \u00B7 ');
return { text, raw };
}
//# sourceMappingURL=usage-quota.js.map
{"version":3,"file":"usage-quota.js","sourceRoot":"","sources":["../../../src/cli/hud/components/usage-quota.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,SAAS,SAAS,CAAC,OAAe;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;IAEjC,IAAI,OAA8B,CAAC;IACnC,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,CAAC;IAClB,CAAC;SAAM,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACxB,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,GAAG,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,IAAI,GACR,OAAO,CAAC,SAAS,CAAC;QAClB,GAAG,CAAC,QAAQ,CAAC;QACb,GAAG;QACH,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,CAAC;IACzB,MAAM,GAAG,GAAG,GAAG,SAAS,GAAG,QAAQ,IAAI,OAAO,GAAG,CAAC;IAClD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,UAAU,CACtC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC;IACvD,MAAM,KAAK,GAAoC,EAAE,CAAC;IAElD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AACvB,CAAC"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function versionBadge(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=version-badge.d.ts.map
{"version":3,"file":"version-badge.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/version-badge.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAyElE,wBAA8B,YAAY,CACxC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAqBjC"}
import { execFile } from 'node:child_process';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { yellow } from '../colors.js';
import { readCache, writeCache } from '../cache.js';
const VERSION_CACHE_KEY = 'version-check';
const VERSION_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
function getCurrentVersion(devflowDir) {
// Try manifest.json first (most reliable for installed version)
try {
const manifestPath = path.join(devflowDir, 'manifest.json');
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
if (typeof manifest.version === 'string')
return manifest.version;
}
catch {
// Fall through
}
// Try package.json as fallback
try {
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
if (typeof pkg.version === 'string')
return pkg.version;
}
catch {
// Fall through
}
return null;
}
function fetchLatestVersion() {
return new Promise((resolve) => {
execFile('npm', ['view', 'devflow-kit', 'version', '--json'], { timeout: 5000 }, (err, stdout) => {
if (err) {
resolve(null);
return;
}
try {
const parsed = JSON.parse(stdout.trim());
resolve(typeof parsed === 'string' ? parsed : null);
}
catch {
const trimmed = stdout.trim();
resolve(trimmed || null);
}
});
});
}
function compareVersions(current, latest) {
const a = current.split('.').map(Number);
const b = latest.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((a[i] || 0) < (b[i] || 0))
return -1;
if ((a[i] || 0) > (b[i] || 0))
return 1;
}
return 0;
}
export default async function versionBadge(ctx) {
const current = getCurrentVersion(ctx.devflowDir);
if (!current)
return null;
// Check cache
let info = readCache(VERSION_CACHE_KEY);
if (!info) {
const latest = await fetchLatestVersion();
if (latest) {
info = { current, latest };
writeCache(VERSION_CACHE_KEY, info, VERSION_CACHE_TTL);
}
}
if (info && compareVersions(info.current, info.latest) < 0) {
const badge = `\u2726 Devflow v${info.latest} \u00B7 update: npx devflow-kit init`;
return { text: yellow(badge), raw: badge };
}
// Don't show version when up to date
return null;
}
//# sourceMappingURL=version-badge.js.map
{"version":3,"file":"version-badge.js","sourceRoot":"","sources":["../../../src/cli/hud/components/version-badge.ts"],"names":[],"mappings":"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"}
import type { ComponentResult, GatherContext } from '../types.js';
export default function worktreeCount(ctx: GatherContext): Promise<ComponentResult | null>;
//# sourceMappingURL=worktree-count.d.ts.map
{"version":3,"file":"worktree-count.d.ts","sourceRoot":"","sources":["../../../src/cli/hud/components/worktree-count.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,wBAA8B,aAAa,CACzC,GAAG,EAAE,aAAa,GACjB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAIjC"}
import { dim } from '../colors.js';
export default async function worktreeCount(ctx) {
if (!ctx.git || ctx.git.worktreeCount <= 1)
return null;
const raw = `${ctx.git.worktreeCount} worktrees`;
return { text: dim(raw), raw };
}
//# sourceMappingURL=worktree-count.js.map
{"version":3,"file":"worktree-count.js","sourceRoot":"","sources":["../../../src/cli/hud/components/worktree-count.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAEnC,MAAM,CAAC,OAAO,CAAC,KAAK,UAAU,aAAa,CACzC,GAAkB;IAElB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,aAAa,YAAY,CAAC;IACjD,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC;AACjC,CAAC"}
import type { HudConfig, ComponentId } from './types.js';
/**
* All 14 HUD components in display order.
*/
export declare const HUD_COMPONENTS: readonly ComponentId[];
export declare function getConfigPath(): string;
export declare function loadConfig(): HudConfig;
export declare function saveConfig(config: HudConfig): void;
export declare function resolveComponents(config: HudConfig): ComponentId[];
//# sourceMappingURL=config.d.ts.map
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/hud/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,WAAW,EAehD,CAAC;AAEF,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,wBAAgB,UAAU,IAAI,SAAS,CAYtC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAOlD;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,WAAW,EAAE,CAIlE"}
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
/**
* All 14 HUD components in display order.
*/
export const HUD_COMPONENTS = [
'directory',
'gitBranch',
'gitAheadBehind',
'diffStats',
'releaseInfo',
'worktreeCount',
'model',
'contextUsage',
'versionBadge',
'sessionDuration',
'sessionCost',
'usageQuota',
'todoProgress',
'configCounts',
];
export function getConfigPath() {
const devflowDir = process.env.DEVFLOW_DIR || path.join(process.env.HOME || homedir(), '.devflow');
return path.join(devflowDir, 'hud.json');
}
export function loadConfig() {
const configPath = getConfigPath();
try {
const raw = fs.readFileSync(configPath, 'utf-8');
const parsed = JSON.parse(raw);
return {
enabled: parsed.enabled !== false,
detail: parsed.detail === true,
};
}
catch {
return { enabled: true, detail: false };
}
}
export function saveConfig(config) {
const configPath = getConfigPath();
const dir = path.dirname(configPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
}
export function resolveComponents(config) {
if (config.enabled)
return [...HUD_COMPONENTS];
// Version badge always renders so users see upgrade notifications
return ['versionBadge'];
}
//# sourceMappingURL=config.js.map
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/hud/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC;;GAEG;AACH,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,WAAW;IACX,WAAW;IACX,gBAAgB;IAChB,WAAW;IACX,aAAa;IACb,eAAe;IACf,OAAO;IACP,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,YAAY;IACZ,cAAc;IACd,cAAc;CACf,CAAC;AAEF,MAAM,UAAU,aAAa;IAC3B,MAAM,UAAU,GACd,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,KAAK;YACjC,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI;SAC/B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAiB;IACjD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,CAAC,GAAG,cAAc,CAAC,CAAC;IAC/C,kEAAkE;IAClE,OAAO,CAAC,cAAc,CAAC,CAAC;AAC1B,CAAC"}
export interface OAuthCredentials {
accessToken: string;
subscriptionType?: string;
}
/** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */
export declare function getClaudeDir(): string;
/** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */
export declare function readCredentialsFile(claudeDir?: string): OAuthCredentials | null;
/** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */
export declare function readKeychainToken(): Promise<string | null>;
/**
* Get OAuth credentials using platform-appropriate strategy.
* macOS: Keychain first, then file fallback. Other platforms: file only.
* Hybrid: if Keychain has token but no subscriptionType, merge from file.
*/
export declare function getCredentials(): Promise<OAuthCredentials | null>;
//# sourceMappingURL=credentials.d.ts.map
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/cli/hud/credentials.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAUD,yEAAyE;AACzE,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAgB/E;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAuC1D;AAED;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAoBvE"}
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { execFile } from 'node:child_process';
const KEYCHAIN_TIMEOUT = 3000; // 3s
const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG;
function debugLog(msg, data) {
if (!DEBUG)
return;
const entry = { ts: new Date().toISOString(), source: 'credentials', msg, ...data };
fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n');
}
/** Resolve the Claude config directory, respecting CLAUDE_CONFIG_DIR. */
export function getClaudeDir() {
return (process.env.CLAUDE_CONFIG_DIR ||
path.join(process.env.HOME || homedir(), '.claude'));
}
/** Read OAuth credentials from ~/.claude/.credentials.json. Injectable claudeDir for tests. */
export function readCredentialsFile(claudeDir) {
try {
const dir = claudeDir ?? getClaudeDir();
const filePath = path.join(dir, '.credentials.json');
const raw = fs.readFileSync(filePath, 'utf-8');
const creds = JSON.parse(raw);
const oauth = creds.claudeAiOauth;
const accessToken = oauth?.accessToken;
if (typeof accessToken !== 'string' || !accessToken)
return null;
const subscriptionType = typeof oauth?.subscriptionType === 'string' ? oauth.subscriptionType : undefined;
debugLog('credentials file read', { filePath, hasSubscriptionType: !!subscriptionType });
return { accessToken, subscriptionType };
}
catch {
return null;
}
}
/** Read OAuth token from macOS Keychain. Returns null on non-darwin or failure. */
export function readKeychainToken() {
if (process.platform !== 'darwin')
return Promise.resolve(null);
return new Promise((resolve) => {
execFile('/usr/bin/security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: KEYCHAIN_TIMEOUT }, (err, stdout) => {
if (err || !stdout.trim()) {
debugLog('keychain read failed', { error: err?.message });
resolve(null);
return;
}
try {
const parsed = JSON.parse(stdout.trim());
const oauth = parsed.claudeAiOauth;
const token = oauth?.accessToken;
if (typeof token === 'string' && token) {
debugLog('keychain token found');
resolve(token);
}
else {
debugLog('keychain: no accessToken in parsed data');
resolve(null);
}
}
catch {
// Keychain value might be the raw token string
const trimmed = stdout.trim();
if (trimmed.length > 20) {
debugLog('keychain: raw token string');
resolve(trimmed);
}
else {
debugLog('keychain: unparseable value');
resolve(null);
}
}
});
});
}
/**
* Get OAuth credentials using platform-appropriate strategy.
* macOS: Keychain first, then file fallback. Other platforms: file only.
* Hybrid: if Keychain has token but no subscriptionType, merge from file.
*/
export async function getCredentials() {
const fileCreds = readCredentialsFile();
if (process.platform !== 'darwin') {
debugLog('non-darwin: file credentials only', { found: !!fileCreds });
return fileCreds;
}
// macOS: try Keychain first
const keychainToken = await readKeychainToken();
if (keychainToken) {
// Merge subscriptionType from file if Keychain doesn't have it
const subscriptionType = fileCreds?.subscriptionType;
debugLog('using keychain token', { hasSubscriptionType: !!subscriptionType });
return { accessToken: keychainToken, subscriptionType };
}
// Fallback to file
debugLog('keychain failed, falling back to file', { found: !!fileCreds });
return fileCreds;
}
//# sourceMappingURL=credentials.js.map
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/cli/hud/credentials.ts"],"names":[],"mappings":"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"}
import type { GitStatus } from './types.js';
/**
* Gather git status for the given working directory.
* Returns null if not in a git repo or on error.
*/
export declare function gatherGitStatus(cwd: string): Promise<GitStatus | null>;
//# sourceMappingURL=git.d.ts.map
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/cli/hud/git.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAgB5C;;;GAGG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAsF5E"}
import { execFile } from 'node:child_process';
const GIT_TIMEOUT = 1000; // 1s per command
function shellExec(cmd, args, cwd) {
return new Promise((resolve) => {
execFile(cmd, args, { cwd, timeout: GIT_TIMEOUT }, (err, stdout) => {
resolve(err ? '' : stdout.trim());
});
});
}
function gitExec(args, cwd) {
return shellExec('git', args, cwd);
}
/**
* Gather git status for the given working directory.
* Returns null if not in a git repo or on error.
*/
export async function gatherGitStatus(cwd) {
// Check if in a git repo
const topLevel = await gitExec(['rev-parse', '--show-toplevel'], cwd);
if (!topLevel)
return null;
// Branch name
const branch = await gitExec(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
if (!branch)
return null;
// Dirty check
const statusOutput = await gitExec(['status', '--porcelain', '--no-optional-locks'], cwd);
let dirty = false;
let staged = false;
for (const line of statusOutput.split('\n')) {
if (line.length < 2)
continue;
const index = line[0];
const worktree = line[1];
// Index column: staged change (A/M/D/R/C)
if (index !== ' ' && index !== '?')
staged = true;
// Worktree column: unstaged change (M/D), or untracked (??)
if (worktree !== ' ' || index === '?')
dirty = true;
}
// Ahead/behind — detect base branch with layered fallback (ported from statusline.sh)
const baseBranch = await detectBaseBranch(branch, cwd);
let ahead = 0;
let behind = 0;
if (baseBranch) {
const revList = await gitExec(['rev-list', '--left-right', '--count', `${baseBranch}...HEAD`], cwd);
const parts = revList.split(/\s+/);
if (parts.length === 2) {
behind = parseInt(parts[0], 10) || 0;
ahead = parseInt(parts[1], 10) || 0;
}
}
// Diff stats against base
let filesChanged = 0;
let additions = 0;
let deletions = 0;
if (baseBranch) {
const diffStat = await gitExec(['diff', '--shortstat', baseBranch], cwd);
const filesMatch = diffStat.match(/(\d+)\s+file/);
const addMatch = diffStat.match(/(\d+)\s+insertion/);
const delMatch = diffStat.match(/(\d+)\s+deletion/);
filesChanged = filesMatch ? parseInt(filesMatch[1], 10) : 0;
additions = addMatch ? parseInt(addMatch[1], 10) : 0;
deletions = delMatch ? parseInt(delMatch[1], 10) : 0;
}
// Tag and worktree info (parallel)
const [tagOutput, worktreeOutput] = await Promise.all([
gitExec(['describe', '--tags', '--abbrev=0'], cwd),
gitExec(['worktree', 'list'], cwd),
]);
const lastTag = tagOutput || null;
let commitsSinceTag = 0;
if (lastTag) {
const countOutput = await gitExec(['rev-list', `${lastTag}..HEAD`, '--count'], cwd);
commitsSinceTag = parseInt(countOutput, 10) || 0;
}
const worktreeCount = worktreeOutput
? worktreeOutput.split('\n').filter(l => l.trim().length > 0).length
: 1;
return {
branch,
dirty,
staged,
ahead,
behind,
filesChanged,
additions,
deletions,
lastTag,
commitsSinceTag,
worktreeCount,
};
}
/**
* Detect the base branch for ahead/behind calculations.
* Uses a 4-layer fallback (ported from statusline.sh):
* 1. Branch reflog ("Created from")
* 2. HEAD reflog ("checkout: moving from X to branch")
* 3. GitHub PR base branch (gh pr view, cached)
* 4. main/master fallback
*/
async function detectBaseBranch(branch, cwd) {
// Layer 1: branch reflog — look for "branch: Created from"
const branchLog = await gitExec(['reflog', 'show', branch, '--format=%gs', '-n', '1'], cwd);
const createdMatch = branchLog.match(/branch: Created from (.+)/);
if (createdMatch) {
const candidate = createdMatch[1];
if (candidate !== 'HEAD' && !candidate.includes('~')) {
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
}
// Layer 2: HEAD reflog — look for "checkout: moving from X to branch"
const headLog = await gitExec(['reflog', 'show', 'HEAD', '--format=%gs'], cwd);
const escapedBranch = branch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const checkoutPattern = new RegExp(`checkout: moving from (\\S+) to ${escapedBranch}`);
for (const line of headLog.split('\n')) {
const match = line.match(checkoutPattern);
if (match) {
const candidate = match[1];
// Skip raw commit hashes and the current branch
if (candidate === branch || /^[0-9a-f]{7,}$/.test(candidate))
continue;
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
}
// Layer 3: GitHub PR base branch via gh CLI
const prBase = await shellExec('gh', ['pr', 'view', '--json', 'baseRefName', '-q', '.baseRefName'], cwd);
if (prBase) {
const exists = await gitExec(['rev-parse', '--verify', prBase], cwd);
if (exists)
return prBase;
}
// Layer 4: main/master fallback (skip if already on that branch — use remote tracking instead)
for (const candidate of ['main', 'master']) {
if (candidate === branch) {
// On main/master itself — compare against remote tracking branch
const remote = await gitExec(['rev-parse', '--verify', `origin/${candidate}`], cwd);
if (remote)
return `origin/${candidate}`;
continue;
}
const exists = await gitExec(['rev-parse', '--verify', candidate], cwd);
if (exists)
return candidate;
}
return null;
}
//# sourceMappingURL=git.js.map
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/cli/hud/git.ts"],"names":[],"mappings":"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"}
export {};
//# sourceMappingURL=index.d.ts.map
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/hud/index.ts"],"names":[],"mappings":""}
import * as fs from 'node:fs';
import * as path from 'node:path';
import { homedir } from 'node:os';
import { readStdin } from './stdin.js';
import { loadConfig, resolveComponents } from './config.js';
import { gatherGitStatus } from './git.js';
import { parseTranscript } from './transcript.js';
import { fetchUsageData } from './usage-api.js';
import { gatherConfigCounts } from './components/config-counts.js';
import { render } from './render.js';
const OVERALL_TIMEOUT = 2000; // 2 second overall timeout
async function main() {
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), OVERALL_TIMEOUT));
try {
const result = await Promise.race([run(), timeoutPromise]);
process.stdout.write(result);
}
catch {
// Timeout or error — output nothing (graceful degradation)
}
}
async function run() {
const stdin = await readStdin();
// Debug: dump raw stdin to file when DEVFLOW_HUD_DEBUG is set
if (process.env.DEVFLOW_HUD_DEBUG) {
fs.writeFileSync(process.env.DEVFLOW_HUD_DEBUG, JSON.stringify(stdin, null, 2));
}
const config = loadConfig();
const resolved = resolveComponents(config);
const components = new Set(resolved);
const cwd = stdin.cwd || process.cwd();
const devflowDir = process.env.DEVFLOW_DIR ||
path.join(process.env.HOME || homedir(), '.devflow');
// Determine what data to gather based on enabled components
const needsGit = components.has('gitBranch') ||
components.has('gitAheadBehind') ||
components.has('diffStats') ||
components.has('releaseInfo') ||
components.has('worktreeCount');
const needsTranscript = components.has('todoProgress') ||
components.has('configCounts');
const needsUsage = components.has('usageQuota');
const needsConfigCounts = components.has('configCounts');
// Parallel data gathering — only fetch what's needed
const [git, transcript, usage] = await Promise.all([
needsGit ? gatherGitStatus(cwd) : Promise.resolve(null),
needsTranscript && stdin.transcript_path
? parseTranscript(stdin.transcript_path)
: Promise.resolve(null),
needsUsage ? fetchUsageData() : Promise.resolve(null),
]);
// Session start time from transcript file creation time
let sessionStartTime = null;
if (stdin.transcript_path) {
try {
const stat = fs.statSync(stdin.transcript_path);
sessionStartTime = stat.birthtime.getTime();
}
catch { /* file may not exist yet */ }
}
// Config counts (fast, synchronous filesystem reads)
const configCountsData = needsConfigCounts
? gatherConfigCounts(cwd)
: null;
// Terminal width via stderr (stdout is piped to Claude Code)
const terminalWidth = process.stderr.columns || 120;
const ctx = {
stdin,
git,
transcript,
usage,
configCounts: configCountsData,
config: { ...config, components: resolved },
devflowDir,
sessionStartTime,
terminalWidth,
};
return render(ctx);
}
main();
//# sourceMappingURL=index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/hud/index.ts"],"names":[],"mappings":"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"}
import type { GatherContext } from './types.js';
/**
* Render all enabled components into a multi-line HUD string.
* Components that return null are excluded. Empty lines are skipped.
*/
export declare function render(ctx: GatherContext): Promise<string>;
//# sourceMappingURL=render.d.ts.map
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/cli/hud/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,aAAa,EAEd,MAAM,YAAY,CAAC;AAsDpB;;;GAGG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA+DhE"}
import { dim } from './colors.js';
import directory from './components/directory.js';
import gitBranch from './components/git-branch.js';
import gitAheadBehind from './components/git-ahead-behind.js';
import diffStats from './components/diff-stats.js';
import model from './components/model.js';
import contextUsage from './components/context-usage.js';
import versionBadge from './components/version-badge.js';
import sessionDuration from './components/session-duration.js';
import usageQuota from './components/usage-quota.js';
import todoProgress from './components/todo-progress.js';
import configCounts from './components/config-counts.js';
import sessionCost from './components/session-cost.js';
import releaseInfo from './components/release-info.js';
import worktreeCount from './components/worktree-count.js';
const COMPONENT_MAP = {
directory,
gitBranch,
gitAheadBehind,
diffStats,
model,
contextUsage,
versionBadge,
sessionDuration,
usageQuota,
todoProgress,
configCounts,
sessionCost,
releaseInfo,
worktreeCount,
};
/**
* Line groupings for smart layout.
* Components are assigned to lines and only rendered if enabled.
* null entries denote section breaks (blank line between sections).
*/
const LINE_GROUPS = [
// Section 1: Info (3 lines)
['directory', 'gitBranch', 'gitAheadBehind', 'releaseInfo', 'worktreeCount', 'diffStats'],
['contextUsage', 'usageQuota'],
['model', 'sessionDuration', 'sessionCost', 'configCounts'],
// --- section break ---
null,
// Section 2: Activity
['todoProgress'],
['versionBadge'],
];
const SEPARATOR = dim(' \u00B7 ');
/**
* Render all enabled components into a multi-line HUD string.
* Components that return null are excluded. Empty lines are skipped.
*/
export async function render(ctx) {
const enabled = new Set(ctx.config.components);
// Render all enabled components in parallel
const results = new Map();
const promises = [];
for (const id of enabled) {
const fn = COMPONENT_MAP[id];
if (!fn)
continue;
promises.push(fn(ctx)
.then((result) => {
if (result)
results.set(id, result);
})
.catch(() => {
/* Component failure is non-fatal */
}));
}
await Promise.all(promises);
// Assemble lines using smart layout with section breaks
const lines = [];
let pendingBreak = false;
for (const entry of LINE_GROUPS) {
if (entry === null) {
if (lines.length > 0)
pendingBreak = true;
continue;
}
const lineResults = entry
.filter((id) => enabled.has(id) && results.has(id))
.map((id) => results.get(id));
if (lineResults.length > 0) {
if (pendingBreak) {
lines.push('');
pendingBreak = false;
}
// Separate multi-line results (containing newlines) from single-line
const singleLine = [];
for (const r of lineResults) {
if (r.text.includes('\n')) {
// Flush any accumulated single-line parts first
if (singleLine.length > 0) {
lines.push(singleLine.join(SEPARATOR));
singleLine.length = 0;
}
lines.push(r.text);
}
else {
singleLine.push(r.text);
}
}
if (singleLine.length > 0) {
lines.push(singleLine.join(SEPARATOR));
}
}
}
return lines.join('\n');
}
//# sourceMappingURL=render.js.map
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/cli/hud/render.ts"],"names":[],"mappings":"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"}
import type { StdinData } from './types.js';
/**
* Read and parse JSON from stdin. Returns empty object on parse failure.
*/
export declare function readStdin(): Promise<StdinData>;
//# sourceMappingURL=stdin.d.ts.map
{"version":3,"file":"stdin.d.ts","sourceRoot":"","sources":["../../src/cli/hud/stdin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;GAEG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAmB9C"}
/**
* Read and parse JSON from stdin. Returns empty object on parse failure.
*/
export function readStdin() {
return new Promise((resolve) => {
let data = '';
process.stdin.setEncoding('utf-8');
process.stdin.on('data', (chunk) => {
data += chunk;
});
process.stdin.on('end', () => {
try {
resolve(JSON.parse(data));
}
catch {
resolve({});
}
});
process.stdin.on('error', () => {
resolve({});
});
process.stdin.resume();
});
}
//# sourceMappingURL=stdin.js.map
{"version":3,"file":"stdin.js","sourceRoot":"","sources":["../../src/cli/hud/stdin.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACzC,IAAI,IAAI,KAAK,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAc,CAAC,CAAC;YACzC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC7B,OAAO,CAAC,EAAE,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC"}
import type { TranscriptData } from './types.js';
/**
* Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress.
* Returns null if the file doesn't exist or can't be parsed.
*/
export declare function parseTranscript(transcriptPath: string): Promise<TranscriptData | null>;
//# sourceMappingURL=transcript.d.ts.map
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/cli/hud/transcript.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAsDhC"}
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as readline from 'node:readline';
// Precompiled patterns
const TODO_WRITE_NAME = /^TodoWrite$/i;
/**
* Parse a Claude Code session transcript (JSONL) to extract tool/agent activity and todo progress.
* Returns null if the file doesn't exist or can't be parsed.
*/
export async function parseTranscript(transcriptPath) {
try {
if (!fs.existsSync(transcriptPath))
return null;
const tools = new Map();
const agents = new Map();
const skills = new Set();
let todosCompleted = 0;
let todosTotal = 0;
const stream = fs.createReadStream(transcriptPath, { encoding: 'utf-8' });
const rl = readline.createInterface({
input: stream,
crlfDelay: Infinity,
});
for await (const line of rl) {
if (!line.trim())
continue;
try {
const entry = JSON.parse(line);
// Turn boundary: clear tools and agents on each human message
// so only the current turn's activity shows in the HUD
if (entry.type === 'human') {
tools.clear();
agents.clear();
continue;
}
const todoResult = processEntry(entry, tools, agents, skills);
if (todoResult) {
todosCompleted = todoResult.completed;
todosTotal = todoResult.total;
}
}
catch {
// Skip malformed lines
}
}
return {
tools: Array.from(tools.values()),
agents: Array.from(agents.values()),
todos: { completed: todosCompleted, total: todosTotal },
skills: Array.from(skills),
};
}
catch {
return null;
}
}
function processEntry(entry, tools, agents, skills) {
if (entry.type !== 'assistant' || !entry.message)
return null;
const message = entry.message;
if (!Array.isArray(message.content))
return null;
let todoResult = null;
for (const block of message.content) {
const blockType = block.type;
if (blockType === 'tool_use') {
const name = block.name;
const id = block.id;
if (name === 'Agent' || name === 'Task') {
// Agent spawn
const input = block.input;
const agentType = input?.subagent_type || 'Agent';
const agentDesc = typeof input?.description === 'string' ? input.description : undefined;
agents.set(id, {
name: agentType,
model: input?.model,
status: 'running',
description: agentDesc,
});
}
else if (name === 'Skill') {
// Track loaded skills
const input = block.input;
if (input?.skill && typeof input.skill === 'string') {
skills.add(input.skill);
}
}
else if (TODO_WRITE_NAME.test(name)) {
// Track todos
const input = block.input;
const todos = input?.todos;
if (Array.isArray(todos)) {
const total = todos.length;
const completed = todos.filter((t) => t.status === 'completed').length;
todoResult = { completed, total };
}
}
else {
// Regular tool — extract file target for Read/Edit/Write
const input = block.input;
let target;
if (input?.file_path && typeof input.file_path === 'string') {
target = path.basename(input.file_path);
}
// Extract description: prefer input.description, fallback to first 4 words of command (Bash)
let description;
if (typeof input?.description === 'string') {
description = input.description;
}
else if (name === 'Bash' && typeof input?.command === 'string') {
description = input.command.split(/\s+/).slice(0, 4).join(' ');
}
tools.set(id, { name, status: 'running', target, description });
}
}
else if (blockType === 'tool_result') {
const toolUseId = block.tool_use_id;
const toolEntry = tools.get(toolUseId);
if (toolEntry) {
toolEntry.status = 'completed';
}
const agentEntry = agents.get(toolUseId);
if (agentEntry) {
agentEntry.status = 'completed';
}
}
}
return todoResult;
}
//# sourceMappingURL=transcript.js.map
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/cli/hud/transcript.ts"],"names":[],"mappings":"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"}
/**
* StdinData — the JSON that Claude Code pipes to statusLine commands.
*/
export interface StdinData {
model?: {
display_name?: string;
id?: string;
};
cwd?: string;
context_window?: {
context_window_size?: number;
current_usage?: {
input_tokens?: number;
output_tokens?: number;
};
used_percentage?: number;
};
cost?: {
total_cost_usd?: number;
};
session_id?: string;
transcript_path?: string;
}
/**
* Component IDs — the 14 HUD components.
*/
export type ComponentId = 'directory' | 'gitBranch' | 'gitAheadBehind' | 'diffStats' | 'model' | 'contextUsage' | 'versionBadge' | 'sessionDuration' | 'usageQuota' | 'todoProgress' | 'configCounts' | 'sessionCost' | 'releaseInfo' | 'worktreeCount';
/**
* HUD config persisted to ~/.devflow/hud.json.
*/
export interface HudConfig {
enabled: boolean;
detail: boolean;
}
/**
* Component render result.
*/
export interface ComponentResult {
text: string;
raw: string;
}
/**
* Component function signature.
*/
export type ComponentFn = (ctx: GatherContext) => Promise<ComponentResult | null>;
/**
* Git status data gathered from the working directory.
*/
export interface GitStatus {
branch: string;
dirty: boolean;
staged: boolean;
ahead: number;
behind: number;
filesChanged: number;
additions: number;
deletions: number;
lastTag: string | null;
commitsSinceTag: number;
worktreeCount: number;
}
/**
* Transcript data parsed from session JSONL.
*/
export interface TranscriptData {
tools: Array<{
name: string;
status: 'running' | 'completed';
target?: string;
description?: string;
}>;
agents: Array<{
name: string;
model?: string;
status: 'running' | 'completed';
description?: string;
}>;
todos: {
completed: number;
total: number;
};
skills: string[];
}
/**
* Usage API data.
*/
export interface UsageData {
fiveHourPercent: number | null;
sevenDayPercent: number | null;
}
/**
* Config counts data for the configCounts component.
*/
export interface ConfigCountsData {
claudeMdFiles: number;
rules: number;
mcpServers: number;
hooks: number;
}
/**
* Gather context passed to all component render functions.
*/
export interface GatherContext {
stdin: StdinData;
git: GitStatus | null;
transcript: TranscriptData | null;
usage: UsageData | null;
configCounts: ConfigCountsData | null;
config: HudConfig & {
components: ComponentId[];
};
devflowDir: string;
sessionStartTime: number | null;
terminalWidth: number;
}
//# sourceMappingURL=types.d.ts.map
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/hud/types.ts"],"names":[],"mappings":"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"}
export {};
//# sourceMappingURL=types.js.map
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/cli/hud/types.ts"],"names":[],"mappings":""}
import type { UsageData } from './types.js';
/**
* Fetch usage quota data from the Anthropic API.
* Uses caching with backoff for rate limiting. Returns null on failure.
*/
export declare function fetchUsageData(): Promise<UsageData | null>;
//# sourceMappingURL=usage-api.d.ts.map
{"version":3,"file":"usage-api.d.ts","sourceRoot":"","sources":["../../src/cli/hud/usage-api.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAqB5C;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAiFhE"}
import * as fs from 'node:fs';
import { readCache, writeCache, readCacheStale } from './cache.js';
import { getCredentials } from './credentials.js';
const USAGE_CACHE_KEY = 'usage';
const USAGE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const USAGE_FAIL_TTL = 15 * 1000; // 15 seconds
const API_TIMEOUT = 1_500; // Must fit within 2s overall HUD timeout
const BACKOFF_CACHE_KEY = 'usage-backoff';
const DEBUG = !!process.env.DEVFLOW_HUD_DEBUG;
function debugLog(msg, data) {
if (!DEBUG)
return;
const entry = { ts: new Date().toISOString(), source: 'usage-api', msg, ...data };
fs.appendFileSync('/tmp/hud-debug.log', JSON.stringify(entry) + '\n');
}
/**
* Fetch usage quota data from the Anthropic API.
* Uses caching with backoff for rate limiting. Returns null on failure.
*/
export async function fetchUsageData() {
// Check backoff
const backoff = readCache(BACKOFF_CACHE_KEY);
if (backoff && Date.now() < backoff.retryAfter) {
debugLog('skipped: backoff active', { retryAfter: backoff.retryAfter });
return readCacheStale(USAGE_CACHE_KEY);
}
// Check cache
const cached = readCache(USAGE_CACHE_KEY);
if (cached)
return cached;
const creds = await getCredentials();
if (!creds) {
debugLog('no OAuth credentials found');
return null;
}
const token = creds.accessToken;
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), API_TIMEOUT);
debugLog('fetching usage', { timeout: API_TIMEOUT });
const response = await fetch('https://api.anthropic.com/api/oauth/usage', {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
'anthropic-beta': 'oauth-2025-04-20',
},
signal: controller.signal,
});
clearTimeout(timeout);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
const delay = Math.min(retryAfter * 1000, 5 * 60 * 1000);
writeCache(BACKOFF_CACHE_KEY, { retryAfter: Date.now() + delay, delay }, delay);
debugLog('rate limited (429)', { retryAfter, delay });
return readCacheStale(USAGE_CACHE_KEY);
}
if (!response.ok) {
debugLog('non-200 response', { status: response.status, statusText: response.statusText });
writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL);
return readCacheStale(USAGE_CACHE_KEY);
}
const body = (await response.json());
const fiveHour = body.five_hour;
const sevenDay = body.seven_day;
const data = {
fiveHourPercent: typeof fiveHour?.utilization === 'number'
? Math.round(Math.max(0, Math.min(100, fiveHour.utilization)))
: null,
sevenDayPercent: typeof sevenDay?.utilization === 'number'
? Math.round(Math.max(0, Math.min(100, sevenDay.utilization)))
: null,
};
debugLog('usage fetched', { fiveHour: data.fiveHourPercent, sevenDay: data.sevenDayPercent });
writeCache(USAGE_CACHE_KEY, data, USAGE_CACHE_TTL);
return data;
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
debugLog('fetch failed', { error: message });
writeCache(USAGE_CACHE_KEY, null, USAGE_FAIL_TTL);
return readCacheStale(USAGE_CACHE_KEY);
}
}
//# sourceMappingURL=usage-api.js.map
{"version":3,"file":"usage-api.js","sourceRoot":"","sources":["../../src/cli/hud/usage-api.ts"],"names":[],"mappings":"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"}
# Stub Detection Patterns
Placeholder implementations that compile but don't deliver real functionality. Flag as **P0-Functionality** issues.
Cross-reference: `core-patterns/references/code-smell-violations.md` covers hardcoded data and fake functionality labeling. This file focuses on structural stub patterns.
---
## 1. Component Stubs
Render nothing meaningful or return placeholder markup.
```tsx
// STUB — returns static text, no real rendering
function UserProfile({ userId }: Props) {
return <div>Name</div>;
}
// REAL — fetches and renders actual data
function UserProfile({ userId }: Props) {
const user = useUser(userId);
if (!user) return <Skeleton />;
return <div><h2>{user.name}</h2><p>{user.email}</p></div>;
}
```
**Patterns to flag:**
- `return null` / `return <></>` in components that should render content
- Empty function bodies (`{}`) for handlers or lifecycle methods
- Components returning only hardcoded strings with no data binding
---
## 2. API / Service Stubs
Functions that exist in signature but throw, return hardcoded values, or do nothing.
```typescript
// STUB — throws instead of implementing
async function createOrder(items: CartItem[]): Promise<Order> {
throw new Error("TODO: implement");
}
// STUB — hardcoded return, no real logic
async function getUser(id: string): Promise<User> {
return { id, name: "Test User", email: "test@test.com" };
}
// REAL — actual implementation
async function createOrder(items: CartItem[]): Promise<Result<Order, OrderError>> {
const validated = validateItems(items);
if (!validated.ok) return validated;
const order = await db.orders.create({ items: validated.value });
await queue.publish("order.created", order);
return { ok: true, value: order };
}
```
**Patterns to flag:**
- `throw new Error("TODO")` / `throw new Error("Not implemented")`
- Functions returning hardcoded objects (no DB/API/computation)
- `"Not implemented"` strings in response bodies
- Empty async functions (`async function foo() {}`)
---
## 3. Hook / Effect Stubs
State and effects declared but not wired to behavior.
```typescript
// STUB — effect does nothing
useEffect(() => {}, [userId]);
// STUB — state declared, setter never called
const [items, setItems] = useState<Item[]>([]);
// ... setItems never appears in the component
// STUB — custom hook returns static value
function usePermissions(): Permissions {
return { canEdit: true, canDelete: false };
}
// REAL — custom hook with actual logic
function usePermissions(): Permissions {
const { user } = useAuth();
const { data } = useQuery(["permissions", user.role], fetchPermissions);
return data ?? DEFAULT_PERMISSIONS;
}
```
**Patterns to flag:**
- `useEffect(() => {}, [...])` — empty effect body
- `useState` where the setter is never called in the component
- Custom hooks returning static/hardcoded values
- `useMemo`/`useCallback` wrapping static values
---
## 4. Wiring Gaps
Individual pieces exist but aren't connected to the running application. **Highest-value detection** — these pass compilation and individual tests but the feature doesn't work end-to-end.
```typescript
// GAP — fetch without await (result discarded)
function loadDashboard() {
fetchMetrics(); // Promise floats, never awaited
return <Dashboard />;
}
// GAP — state declared but never rendered
const [error, setError] = useState<string | null>(null);
// ... error never appears in JSX
// GAP — handler defined but not bound
function handleSubmit(data: FormData) { /* real logic */ }
// ... <form onSubmit={handleSubmit}> never appears
// GAP — route defined with no-op handler
app.post("/api/orders", (_req, res) => {
res.status(200).json({ ok: true });
});
// GAP — env var read but unused
const API_KEY = process.env.STRIPE_API_KEY;
// ... API_KEY never passed to any client or fetch call
```
**Patterns to flag:**
- `fetch`/`axios`/API call without `await` or `.then` (result discarded)
- State variable (`useState`, `useRef`) never rendered or read in output
- Event handler defined but not bound to any element/listener
- Route/endpoint registered with empty or no-op handler
- Environment variable or config read but never used downstream
- Import used only in type position but imported as value (in non-type-only import)
+31
-0

@@ -8,2 +8,32 @@ # Changelog

## [1.8.0] - 2026-03-22
### Added
- **Configurable HUD** replacing bash statusline — 14 components, on/off model (#155)
- **HUD components**: directory, git branch, ahead/behind, diff stats, release info, worktree count, model, context usage, version badge, session duration, session cost, usage quota, todo progress, config counts
- **`--hud-only` flag** for standalone HUD install
- **`--no-hud` flag** to skip HUD during init
- **`devflow hud` command** (--status, --enable, --disable, --detail, --no-detail)
- **Version upgrade notification**: `✦ Devflow vX.Y.Z · update: npx devflow-kit init` (yellow, always visible even when HUD disabled)
- **Skill shadowing docs** and HUD options added to README (#156)
- **Simplifier agent** — 8 structured slop detection categories (#120)
- **Scrutinizer agent** — stub detection patterns with reference file (#121)
- **Shepherd agent** — goal-backward verification, artifact depth checking, stub type, re-verification (#124)
### Changed
- Init flow: HUD preset picker (5 options) → simple yes/no confirm
- `--disable` keeps statusLine registered (version badge still renders)
- Manifest `features.hud` field: `string|false` → `boolean`
### Fixed
- HUD base branch detection matching raw commit hashes from reflog (#156)
- HUD comparing main vs main (0/0 always) — now compares against origin/main
### Removed
- HUD preset system (minimal/classic/standard/full)
- `--configure`, `--preset`, `--hud <preset>` flags
- `speed`, `tool-activity`, `agent-activity` components
---
## [1.7.0] - 2026-03-20

@@ -932,2 +962,3 @@

[Unreleased]: https://github.com/dean0x/devflow/compare/v1.4.0...HEAD
[1.8.0]: https://github.com/dean0x/devflow/compare/v1.7.0...v1.8.0
[1.7.0]: https://github.com/dean0x/devflow/compare/v1.6.1...v1.7.0

@@ -934,0 +965,0 @@ [1.6.1]: https://github.com/dean0x/devflow/compare/v1.6.0...v1.6.1

+3
-1

@@ -12,2 +12,3 @@ #!/usr/bin/env node

import { skillsCommand } from './commands/skills.js';
import { hudCommand } from './commands/hud.js';
const __filename = fileURLToPath(import.meta.url);

@@ -23,3 +24,3 @@ const __dirname = dirname(__filename);

.helpOption('-h, --help', 'Display help information')
.addHelpText('after', '\nExamples:\n $ devflow init Install all DevFlow plugins\n $ devflow init --plugin=implement Install specific plugin\n $ devflow init --plugin=implement,review Install multiple plugins\n $ devflow list List available plugins\n $ devflow ambient --enable Enable always-on ambient mode\n $ devflow memory --status Check working memory state\n $ devflow uninstall Remove DevFlow from Claude Code\n $ devflow --version Show version\n $ devflow --help Show help\n\nDocumentation:\n https://github.com/dean0x/devflow#readme');
.addHelpText('after', '\nExamples:\n $ devflow init Install all DevFlow plugins\n $ devflow init --plugin=implement Install specific plugin\n $ devflow init --plugin=implement,review Install multiple plugins\n $ devflow list List available plugins\n $ devflow ambient --enable Enable always-on ambient mode\n $ devflow memory --status Check working memory state\n $ devflow hud --configure Configure HUD preset\n $ devflow uninstall Remove DevFlow from Claude Code\n $ devflow --version Show version\n $ devflow --help Show help\n\nDocumentation:\n https://github.com/dean0x/devflow#readme');
// Register commands

@@ -32,2 +33,3 @@ program.addCommand(initCommand);

program.addCommand(skillsCommand);
program.addCommand(hudCommand);
// Handle no command

@@ -34,0 +36,0 @@ program.action(() => {

@@ -6,2 +6,3 @@ import { Command } from 'commander';

export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js';
export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js';
/**

@@ -8,0 +9,0 @@ * Parse a comma-separated plugin selection string into normalized plugin names.

@@ -11,3 +11,3 @@ import { Command } from 'commander';

import { isClaudeCliAvailable } from '../utils/cli.js';
import { installViaCli, installViaFileCopy } from '../utils/installer.js';
import { installViaCli, installViaFileCopy, copyDirectory } from '../utils/installer.js';
import { installSettings, installManagedSettings, installClaudeignore, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js';

@@ -19,2 +19,4 @@ import { DEVFLOW_PLUGINS, LEGACY_SKILL_NAMES, LEGACY_COMMAND_NAMES, buildAssetMaps } from '../plugins.js';

import { addMemoryHooks, removeMemoryHooks } from './memory.js';
import { addHudStatusLine, removeHudStatusLine } from './hud.js';
import { loadConfig as loadHudConfig, saveConfig as saveHudConfig } from '../hud/config.js';
import { readManifest, writeManifest, resolvePluginList, detectUpgrade } from '../utils/manifest.js';

@@ -25,2 +27,3 @@ // Re-export pure functions for tests (canonical source is post-install.ts)

export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js';
export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js';
const __filename = fileURLToPath(import.meta.url);

@@ -72,2 +75,4 @@ const __dirname = dirname(__filename);

.option('--no-memory', 'Disable working memory hooks')
.option('--no-hud', 'Disable HUD status line')
.option('--hud-only', 'Install only the HUD (no plugins, hooks, or extras)')
.action(async (options) => {

@@ -89,3 +94,7 @@ // Get package version

let scope = 'user';
if (options.scope) {
if (options.hudOnly) {
// --hud-only: skip scope prompt, always user scope
scope = 'user';
}
else if (options.scope) {
const normalizedScope = options.scope.toLowerCase();

@@ -116,2 +125,66 @@ if (normalizedScope !== 'user' && normalizedScope !== 'local') {

}
// --hud-only: install only HUD (skip plugins, hooks, extras)
if (options.hudOnly) {
// Resolve paths
const paths = await getInstallationPaths(scope);
const claudeDir = paths.claudeDir;
const devflowDir = paths.devflowDir;
// Save HUD config
const existingHud = loadHudConfig();
saveHudConfig({ enabled: true, detail: existingHud.detail });
// Update statusLine in settings.json
const settingsPath = path.join(claudeDir, 'settings.json');
try {
let content;
try {
content = await fs.readFile(settingsPath, 'utf-8');
}
catch {
content = '{}';
}
const updated = addHudStatusLine(content, devflowDir);
await fs.writeFile(settingsPath, updated, 'utf-8');
}
catch (error) {
p.log.error(`Failed to update settings: ${error instanceof Error ? error.message : error}`);
process.exit(1);
}
// Copy HUD scripts to devflow dir
const rootDir = path.resolve(__dirname, '..', '..');
const scriptsSource = path.join(rootDir, 'scripts');
const scriptsTarget = path.join(devflowDir, 'scripts');
try {
await fs.mkdir(scriptsTarget, { recursive: true });
// Copy hud.sh
await fs.copyFile(path.join(scriptsSource, 'hud.sh'), path.join(scriptsTarget, 'hud.sh'));
// Copy hud/ directory
const hudSource = path.join(scriptsSource, 'hud');
const hudTarget = path.join(scriptsTarget, 'hud');
await copyDirectory(hudSource, hudTarget);
if (process.platform !== 'win32') {
await fs.chmod(path.join(scriptsTarget, 'hud.sh'), 0o755);
}
}
catch (error) {
p.log.error(`Failed to copy HUD scripts: ${error instanceof Error ? error.message : error}`);
process.exit(1);
}
// Write minimal manifest
const now = new Date().toISOString();
try {
await writeManifest(devflowDir, {
version,
plugins: [],
scope,
features: { teams: false, ambient: false, memory: false, hud: true },
installedAt: now,
updatedAt: now,
});
}
catch { /* non-fatal */ }
p.log.success('HUD installed');
p.log.info(`Configure later: ${color.cyan('devflow hud --status')}`);
p.outro(color.green('HUD-only install complete.'));
return;
}
// Select plugins to install

@@ -214,2 +287,21 @@ let selectedPlugins = [];

}
// HUD selection (yes/no)
let hudEnabled;
if (options.hud !== undefined) {
hudEnabled = options.hud;
}
else if (!process.stdin.isTTY) {
hudEnabled = true;
}
else {
const hudChoice = await p.confirm({
message: 'Enable HUD status line?',
initialValue: true,
});
if (p.isCancel(hudChoice)) {
p.cancel('Installation cancelled.');
process.exit(0);
}
hudEnabled = hudChoice;
}
// Security deny list placement (user scope + TTY only)

@@ -452,2 +544,19 @@ let securityMode = 'user';

}
// Configure HUD
const existingHud = loadHudConfig();
saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail });
// Update statusLine in settings.json (add or remove based on choice)
try {
const hudContent = await fs.readFile(settingsPath, 'utf-8');
const hudUpdated = hudEnabled
? addHudStatusLine(hudContent, devflowDir)
: removeHudStatusLine(hudContent);
if (hudUpdated !== hudContent) {
await fs.writeFile(settingsPath, hudUpdated, 'utf-8');
if (verbose) {
p.log.info(`HUD ${hudEnabled ? 'enabled' : 'disabled'}`);
}
}
}
catch { /* settings.json may not exist yet */ }
}

@@ -553,3 +662,3 @@ const fileExtras = selectedExtras.filter(e => e !== 'settings' && e !== 'safe-delete');

scope,
features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled },
features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled, hud: hudEnabled },
installedAt: existingManifest?.installedAt ?? now,

@@ -556,0 +665,0 @@ updatedAt: now,

@@ -18,2 +18,3 @@ import { Command } from 'commander';

features.memory ? 'memory' : null,
features.hud ? 'hud' : null,
].filter(Boolean).join(', ') || 'none';

@@ -20,0 +21,0 @@ }

@@ -14,2 +14,3 @@ import { Command } from 'commander';

import { removeMemoryHooks } from './memory.js';
import { removeHudStatusLine } from './hud.js';
import { listShadowed } from './skills.js';

@@ -364,21 +365,13 @@ import { detectShell, getProfilePath } from '../utils/safe-delete.js';

const settingsPath = path.join(paths.claudeDir, 'settings.json');
let settingsContent = await fs.readFile(settingsPath, 'utf-8');
// Always remove ambient hook on full uninstall (idempotent)
const withoutAmbient = removeAmbientHook(settingsContent);
if (withoutAmbient !== settingsContent) {
await fs.writeFile(settingsPath, withoutAmbient, 'utf-8');
settingsContent = withoutAmbient;
const originalContent = await fs.readFile(settingsPath, 'utf-8');
// Remove all DevFlow hooks in one pass (idempotent)
let settingsContent = removeAmbientHook(originalContent);
settingsContent = removeMemoryHooks(settingsContent);
settingsContent = removeHudStatusLine(settingsContent);
if (settingsContent !== originalContent) {
await fs.writeFile(settingsPath, settingsContent, 'utf-8');
if (verbose) {
p.log.success(`Ambient mode hook removed from settings.json (${scope})`);
p.log.success(`DevFlow hooks removed from settings.json (${scope})`);
}
}
// Always remove memory hooks on full uninstall (idempotent)
const withoutMemory = removeMemoryHooks(settingsContent);
if (withoutMemory !== settingsContent) {
await fs.writeFile(settingsPath, withoutMemory, 'utf-8');
settingsContent = withoutMemory;
if (verbose) {
p.log.success(`Memory hooks removed from settings.json (${scope})`);
}
}
const settings = JSON.parse(settingsContent);

@@ -385,0 +378,0 @@ if (settings.hooks) {

@@ -12,2 +12,3 @@ /**

memory: boolean;
hud?: boolean;
};

@@ -14,0 +15,0 @@ installedAt: string;

{
"name": "devflow-kit",
"version": "1.7.0",
"version": "1.8.0",
"description": "Agentic Development Toolkit for Claude Code - Enhance AI-assisted development with intelligent commands and workflows",

@@ -15,2 +15,4 @@ "type": "module",

"scripts/hooks/",
"scripts/hud.sh",
"scripts/hud/",
"src/templates/",

@@ -22,5 +24,6 @@ "README.md",

"scripts": {
"build": "npm run build:cli && npm run build:plugins",
"build": "npm run build:cli && npm run build:plugins && npm run build:hud",
"build:cli": "tsc",
"build:plugins": "npx tsx scripts/build-plugins.ts",
"build:hud": "node scripts/build-hud.js",
"dev": "tsc --watch",

@@ -27,0 +30,0 @@ "cli": "node dist/cli.js",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -24,10 +24,12 @@ ---

3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues.
4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
6. **Report status**: Return structured report with pillar evaluations and changes made.
6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
7. **Report status**: Return structured report with pillar evaluations and changes made.
## Principles

@@ -34,0 +36,0 @@

@@ -24,6 +24,16 @@ ---

2. **Review implementation**: Read FILES_CHANGED to understand what was built
3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
4. **Check scope**: Identify out-of-scope additions not justified by design improvements
5. **Report misalignments**: Document issues with sufficient detail for Coder to fix
3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth.
4. **Check artifact depth**: Classify each deliverable using this scale:
| Depth | Meaning | Example |
|-------|---------|---------|
| Exists | File/function created | Route file exists |
| Substantive | Contains real logic | Route has validation + DB call |
| Wired | Connected to running app | Route registered, imported, reachable |
Flag anything at "Exists" without reaching "Wired" as `incomplete`.
5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
6. **Check scope**: Identify out-of-scope additions not justified by design improvements
7. **Report misalignments**: Document issues with sufficient detail for Coder to fix
## Principles

@@ -55,2 +65,7 @@

### Artifact Depth
| Deliverable | Exists | Substantive | Wired | Status |
|-------------|--------|-------------|-------|--------|
| {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub |
### Misalignments Found (if MISALIGNED)

@@ -64,2 +79,3 @@

| intent_drift | {how intent drifted} | {file paths} | {how to realign} |
| stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} |

@@ -69,13 +85,9 @@ ### Scope Check

- Justification: {if additions found, are they justified design improvements?}
### Re-verification (if applicable)
| Previously Failed | Status | Notes |
|-------------------|--------|-------|
| {item} | RESOLVED/STILL_FAILING | {details} |
```
## Misalignment Types
| Type | Description | Example |
|------|-------------|---------|
| `missing` | Functionality in plan not implemented | "Login validation not implemented" |
| `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" |
| `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" |
| `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" |
## Boundaries

@@ -88,2 +100,3 @@

- Intent drift
- Stubs or placeholders passing as real implementations

@@ -95,2 +108,3 @@ **Report as ALIGNED:**

- Implementation matches original intent
- All deliverables reach "Wired" depth

@@ -97,0 +111,0 @@ **Never:**

@@ -33,22 +33,30 @@ ---

3. **Enhance Clarity**: Simplify code structure by:
3. **Remove Slop**: Detect and remove these categories:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
| Category | Pattern |
|----------|---------|
| Language-behavior tests | Tests verifying built-in language features work as documented |
| Redundant type checks | Runtime checks for types TypeScript already enforces |
| Over-defensive handling | try/catch around code that cannot throw |
| Debug remnants | console.log, debugger, alert() left behind |
| Commented-out code | Dead code preserved in comments |
| Unused imports | Imports not referenced anywhere in file |
| Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) |
| Unnecessary intermediates | Variables used once, immediately after assignment |
4. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary nesting (early returns, guard clauses)
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
- Avoiding nested ternary operators — prefer switch or if/else
- Choosing clarity over brevity — explicit code beats compact code
4. **Maintain Balance**: Avoid over-simplification that could:
5. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

@@ -58,3 +66,3 @@ Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
2. Analyze for slop categories and clarity improvements
3. Apply project-specific best practices and coding standards

@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged

**Handle autonomously:**
- Naming improvements, dead code removal, nesting reduction
- Slop removal (all 8 categories)
- Naming improvements, nesting reduction
- Import sorting and organization
- Redundant abstraction elimination
- Comment cleanup (remove obvious, keep non-obvious)
- Comment cleanup (remove obvious, keep non-obvious)

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -24,10 +24,12 @@ ---

3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues.
4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
6. **Report status**: Return structured report with pillar evaluations and changes made.
6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
7. **Report status**: Return structured report with pillar evaluations and changes made.
## Principles

@@ -34,0 +36,0 @@

@@ -24,6 +24,16 @@ ---

2. **Review implementation**: Read FILES_CHANGED to understand what was built
3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
4. **Check scope**: Identify out-of-scope additions not justified by design improvements
5. **Report misalignments**: Document issues with sufficient detail for Coder to fix
3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth.
4. **Check artifact depth**: Classify each deliverable using this scale:
| Depth | Meaning | Example |
|-------|---------|---------|
| Exists | File/function created | Route file exists |
| Substantive | Contains real logic | Route has validation + DB call |
| Wired | Connected to running app | Route registered, imported, reachable |
Flag anything at "Exists" without reaching "Wired" as `incomplete`.
5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
6. **Check scope**: Identify out-of-scope additions not justified by design improvements
7. **Report misalignments**: Document issues with sufficient detail for Coder to fix
## Principles

@@ -55,2 +65,7 @@

### Artifact Depth
| Deliverable | Exists | Substantive | Wired | Status |
|-------------|--------|-------------|-------|--------|
| {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub |
### Misalignments Found (if MISALIGNED)

@@ -64,2 +79,3 @@

| intent_drift | {how intent drifted} | {file paths} | {how to realign} |
| stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} |

@@ -69,13 +85,9 @@ ### Scope Check

- Justification: {if additions found, are they justified design improvements?}
### Re-verification (if applicable)
| Previously Failed | Status | Notes |
|-------------------|--------|-------|
| {item} | RESOLVED/STILL_FAILING | {details} |
```
## Misalignment Types
| Type | Description | Example |
|------|-------------|---------|
| `missing` | Functionality in plan not implemented | "Login validation not implemented" |
| `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" |
| `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" |
| `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" |
## Boundaries

@@ -88,2 +100,3 @@

- Intent drift
- Stubs or placeholders passing as real implementations

@@ -95,2 +108,3 @@ **Report as ALIGNED:**

- Implementation matches original intent
- All deliverables reach "Wired" depth

@@ -97,0 +111,0 @@ **Never:**

@@ -33,22 +33,30 @@ ---

3. **Enhance Clarity**: Simplify code structure by:
3. **Remove Slop**: Detect and remove these categories:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
| Category | Pattern |
|----------|---------|
| Language-behavior tests | Tests verifying built-in language features work as documented |
| Redundant type checks | Runtime checks for types TypeScript already enforces |
| Over-defensive handling | try/catch around code that cannot throw |
| Debug remnants | console.log, debugger, alert() left behind |
| Commented-out code | Dead code preserved in comments |
| Unused imports | Imports not referenced anywhere in file |
| Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) |
| Unnecessary intermediates | Variables used once, immediately after assignment |
4. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary nesting (early returns, guard clauses)
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
- Avoiding nested ternary operators — prefer switch or if/else
- Choosing clarity over brevity — explicit code beats compact code
4. **Maintain Balance**: Avoid over-simplification that could:
5. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

@@ -58,3 +66,3 @@ Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
2. Analyze for slop categories and clarity improvements
3. Apply project-specific best practices and coding standards

@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged

**Handle autonomously:**
- Naming improvements, dead code removal, nesting reduction
- Slop removal (all 8 categories)
- Naming improvements, nesting reduction
- Import sorting and organization
- Redundant abstraction elimination
- Comment cleanup (remove obvious, keep non-obvious)
- Comment cleanup (remove obvious, keep non-obvious)

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -33,22 +33,30 @@ ---

3. **Enhance Clarity**: Simplify code structure by:
3. **Remove Slop**: Detect and remove these categories:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
| Category | Pattern |
|----------|---------|
| Language-behavior tests | Tests verifying built-in language features work as documented |
| Redundant type checks | Runtime checks for types TypeScript already enforces |
| Over-defensive handling | try/catch around code that cannot throw |
| Debug remnants | console.log, debugger, alert() left behind |
| Commented-out code | Dead code preserved in comments |
| Unused imports | Imports not referenced anywhere in file |
| Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) |
| Unnecessary intermediates | Variables used once, immediately after assignment |
4. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary nesting (early returns, guard clauses)
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
- Avoiding nested ternary operators — prefer switch or if/else
- Choosing clarity over brevity — explicit code beats compact code
4. **Maintain Balance**: Avoid over-simplification that could:
5. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

@@ -58,3 +66,3 @@ Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
2. Analyze for slop categories and clarity improvements
3. Apply project-specific best practices and coding standards

@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged

**Handle autonomously:**
- Naming improvements, dead code removal, nesting reduction
- Slop removal (all 8 categories)
- Naming improvements, nesting reduction
- Import sorting and organization
- Redundant abstraction elimination
- Comment cleanup (remove obvious, keep non-obvious)
- Comment cleanup (remove obvious, keep non-obvious)

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -24,10 +24,12 @@ ---

3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues.
4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
6. **Report status**: Return structured report with pillar evaluations and changes made.
6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
7. **Report status**: Return structured report with pillar evaluations and changes made.
## Principles

@@ -34,0 +36,0 @@

@@ -33,22 +33,30 @@ ---

3. **Enhance Clarity**: Simplify code structure by:
3. **Remove Slop**: Detect and remove these categories:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
| Category | Pattern |
|----------|---------|
| Language-behavior tests | Tests verifying built-in language features work as documented |
| Redundant type checks | Runtime checks for types TypeScript already enforces |
| Over-defensive handling | try/catch around code that cannot throw |
| Debug remnants | console.log, debugger, alert() left behind |
| Commented-out code | Dead code preserved in comments |
| Unused imports | Imports not referenced anywhere in file |
| Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) |
| Unnecessary intermediates | Variables used once, immediately after assignment |
4. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary nesting (early returns, guard clauses)
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
- Avoiding nested ternary operators — prefer switch or if/else
- Choosing clarity over brevity — explicit code beats compact code
4. **Maintain Balance**: Avoid over-simplification that could:
5. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

@@ -58,3 +66,3 @@ Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
2. Analyze for slop categories and clarity improvements
3. Apply project-specific best practices and coding standards

@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged

**Handle autonomously:**
- Naming improvements, dead code removal, nesting reduction
- Slop removal (all 8 categories)
- Naming improvements, nesting reduction
- Import sorting and organization
- Redundant abstraction elimination
- Comment cleanup (remove obvious, keep non-obvious)
- Comment cleanup (remove obvious, keep non-obvious)

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -7,3 +7,3 @@ {

},
"version": "1.7.0",
"version": "1.8.0",
"homepage": "https://github.com/dean0x/devflow",

@@ -10,0 +10,0 @@ "repository": "https://github.com/dean0x/devflow",

@@ -27,3 +27,3 @@ # DevFlow

- **Parallel debugging** — competing hypotheses investigated simultaneously
- **Version notifications** — statusline shows an update badge when a newer version is available
- **Version notifications** — HUD shows `✦ Devflow vX.Y.Z · update: npx devflow-kit init` when a newer version is available
- **35 quality skills** — 9 auto-activating core, 8 optional language/ecosystem, plus specialized review, agent, and orchestration skills

@@ -252,4 +252,34 @@

| `--memory` / `--no-memory` | Enable/disable working memory (default: on) |
| `--hud-only` | Install only the HUD (no plugins, hooks, or extras) |
| `--no-hud` | Disable HUD status line |
| `--verbose` | Show detailed output |
### HUD Options
| Command | Description |
|---------|-------------|
| `npx devflow-kit hud --status` | Show current HUD config |
| `npx devflow-kit hud --enable` | Enable HUD |
| `npx devflow-kit hud --disable` | Disable HUD (version notifications still appear) |
| `npx devflow-kit hud --detail` | Show tool/agent descriptions |
| `npx devflow-kit hud --no-detail` | Hide tool/agent descriptions |
### Skill Shadowing
Override any DevFlow skill with your own version. Shadowed skills survive `devflow init` — they won't be overwritten on reinstall or upgrade.
```bash
# Create a personal override (copies current version as reference)
npx devflow-kit skills shadow core-patterns
# Edit your override
vim ~/.claude/skills/core-patterns/SKILL.md
# List all overrides
npx devflow-kit skills list-shadowed
# Remove override (next init restores DevFlow's version)
npx devflow-kit skills unshadow core-patterns
```
### Uninstall Options

@@ -256,0 +286,0 @@

@@ -24,10 +24,12 @@ ---

3. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
3. **Detect stubs and wiring gaps**: Check for placeholder implementations that compile but don't deliver real functionality. See `references/stub-detection.md` for patterns. Flag as P0-Functionality issues.
4. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
4. **Evaluate P1 pillars** (Complexity, Error Handling, Tests): These SHOULD pass. Fix all issues found.
5. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
5. **Evaluate P2 pillars** (Naming, Consistency, Documentation): Report as suggestions. Fix if straightforward.
6. **Report status**: Return structured report with pillar evaluations and changes made.
6. **Commit fixes**: If any changes were made, create a commit with message "fix: address self-review issues".
7. **Report status**: Return structured report with pillar evaluations and changes made.
## Principles

@@ -34,0 +36,0 @@

@@ -24,6 +24,16 @@ ---

2. **Review implementation**: Read FILES_CHANGED to understand what was built
3. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
4. **Check scope**: Identify out-of-scope additions not justified by design improvements
5. **Report misalignments**: Document issues with sufficient detail for Coder to fix
3. **Goal-backward verification**: Start from the user's observable goals. For each goal: trace backward through the implementation — is it wired into the running app → does it contain substantive logic → does the file/function exist? Report any goal failing at any depth.
4. **Check artifact depth**: Classify each deliverable using this scale:
| Depth | Meaning | Example |
|-------|---------|---------|
| Exists | File/function created | Route file exists |
| Substantive | Contains real logic | Route has validation + DB call |
| Wired | Connected to running app | Route registered, imported, reachable |
Flag anything at "Exists" without reaching "Wired" as `incomplete`.
5. **Check completeness**: Verify all plan steps implemented, all acceptance criteria met
6. **Check scope**: Identify out-of-scope additions not justified by design improvements
7. **Report misalignments**: Document issues with sufficient detail for Coder to fix
## Principles

@@ -55,2 +65,7 @@

### Artifact Depth
| Deliverable | Exists | Substantive | Wired | Status |
|-------------|--------|-------------|-------|--------|
| {feature} | Y/N | Y/N | Y/N | complete/incomplete/stub |
### Misalignments Found (if MISALIGNED)

@@ -64,2 +79,3 @@

| intent_drift | {how intent drifted} | {file paths} | {how to realign} |
| stub | {placeholder, not real logic} | {file paths} | {what real implementation needs} |

@@ -69,13 +85,9 @@ ### Scope Check

- Justification: {if additions found, are they justified design improvements?}
### Re-verification (if applicable)
| Previously Failed | Status | Notes |
|-------------------|--------|-------|
| {item} | RESOLVED/STILL_FAILING | {details} |
```
## Misalignment Types
| Type | Description | Example |
|------|-------------|---------|
| `missing` | Functionality in plan not implemented | "Login validation not implemented" |
| `scope_creep` | Added functionality not in plan | "Analytics tracking added but not requested" |
| `incomplete` | Partially implemented functionality | "Error handling added but no user-facing messages" |
| `intent_drift` | Implementation solves different problem | "Built password reset instead of login flow" |
## Boundaries

@@ -88,2 +100,3 @@

- Intent drift
- Stubs or placeholders passing as real implementations

@@ -95,2 +108,3 @@ **Report as ALIGNED:**

- Implementation matches original intent
- All deliverables reach "Wired" depth

@@ -97,0 +111,0 @@ **Never:**

@@ -33,22 +33,30 @@ ---

3. **Enhance Clarity**: Simplify code structure by:
3. **Remove Slop**: Detect and remove these categories:
- Reducing unnecessary complexity and nesting
- Eliminating redundant code and abstractions
- Improving readability through clear variable and function names
| Category | Pattern |
|----------|---------|
| Language-behavior tests | Tests verifying built-in language features work as documented |
| Redundant type checks | Runtime checks for types TypeScript already enforces |
| Over-defensive handling | try/catch around code that cannot throw |
| Debug remnants | console.log, debugger, alert() left behind |
| Commented-out code | Dead code preserved in comments |
| Unused imports | Imports not referenced anywhere in file |
| Verbose names | Unnecessarily long names (`currentUserDataObject` → `user`) |
| Unnecessary intermediates | Variables used once, immediately after assignment |
4. **Enhance Clarity**: Simplify code structure by:
- Reducing unnecessary nesting (early returns, guard clauses)
- Consolidating related logic
- Removing unnecessary comments that describe obvious code
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
- Choose clarity over brevity - explicit code is often better than overly compact code
- Avoiding nested ternary operators — prefer switch or if/else
- Choosing clarity over brevity — explicit code beats compact code
4. **Maintain Balance**: Avoid over-simplification that could:
5. **Maintain Balance**: Avoid over-simplification that could:
- Reduce code clarity or maintainability
- Create overly clever solutions that are hard to understand
- Combine too many concerns into single functions or components
- Remove helpful abstractions that improve code organization
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
- Make the code harder to debug or extend
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
6. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.

@@ -58,3 +66,3 @@ Your refinement process:

1. Identify the recently modified code sections
2. Analyze for opportunities to improve elegance and consistency
2. Analyze for slop categories and clarity improvements
3. Apply project-specific best practices and coding standards

@@ -92,5 +100,6 @@ 4. Ensure all functionality remains unchanged

**Handle autonomously:**
- Naming improvements, dead code removal, nesting reduction
- Slop removal (all 8 categories)
- Naming improvements, nesting reduction
- Import sorting and organization
- Redundant abstraction elimination
- Comment cleanup (remove obvious, keep non-obvious)
- Comment cleanup (remove obvious, keep non-obvious)
{
"statusLine": {
"type": "command",
"command": "${DEVFLOW_DIR}/scripts/statusline.sh"
"command": "${DEVFLOW_DIR}/scripts/hud.sh"
},

@@ -6,0 +6,0 @@ "hooks": {