FABRIC/src/config.ts
jedarden 982f93162c feat(cli): add fabric config command for configuration management
Add a new `fabric config` command that provides a user-friendly interface
to manage FABRIC configuration without manual file editing.

Features:
- `fabric config` - Show current configuration (theme, presets, recent commands, filter state)
- `fabric config theme [theme]` - Show or set theme (dark/light)
- `fabric config presets list` - List all focus presets
- `fabric config presets delete <name>` - Delete a focus preset
- `fabric config clear` - Clear configuration state (with --theme, --presets, --commands, --filters, --all options)

Config files managed:
- ~/.fabric/theme.json (theme preference)
- ~/.fabric/focus-presets.json (focus mode presets)
- ~/.fabric/recent-commands.json (command history)
- ~/.fabric-filter-state.json (filter persistence)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 22:21:21 -04:00

337 lines
9 KiB
TypeScript

/**
* FABRIC Config CLI Command
*
* Usage:
* fabric config - Show current configuration
* fabric config theme - Show/set theme
* fabric config presets - Manage focus presets
* fabric config clear - Clear all config state
*/
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { Command } from 'commander';
import { createTuiPresetManager } from './focusPresets.js';
const HOME = process.env.HOME || '';
const CONFIG_DIR = path.join(HOME, '.fabric');
/**
* Config file paths
*/
const CONFIG_FILES = {
theme: path.join(CONFIG_DIR, 'theme.json'),
presets: path.join(CONFIG_DIR, 'focus-presets.json'),
recentCommands: path.join(CONFIG_DIR, 'recent-commands.json'),
filterState: path.join(HOME, '.fabric-filter-state.json'),
};
/**
* Show current configuration
*/
function showConfig(): void {
console.log('FABRIC Configuration');
console.log('===================');
console.log(`Config directory: ${CONFIG_DIR}`);
console.log('');
// Theme
console.log('Theme:');
const theme = loadTheme();
console.log(` Current: ${theme}`);
console.log(` File: ${CONFIG_FILES.theme}`);
console.log('');
// Presets
console.log('Focus Presets:');
const presetManager = createTuiPresetManager();
const presets = presetManager.getPresets();
if (presets.length === 0) {
console.log(' No presets saved');
} else {
console.log(` Count: ${presets.length}`);
presets.forEach(p => {
const createdAt = new Date(p.createdAt).toLocaleString();
console.log(` - ${p.name}`);
console.log(` Workers: ${p.pinnedWorkers.length}, Beads: ${p.pinnedBeads.length}`);
console.log(` Created: ${createdAt}`);
if (p.description) {
console.log(` Description: ${p.description}`);
}
});
}
console.log(` File: ${CONFIG_FILES.presets}`);
console.log('');
// Recent commands
console.log('Recent Commands:');
const recentCommands = loadRecentCommands();
if (recentCommands.length === 0) {
console.log(' No recent commands');
} else {
console.log(` Count: ${recentCommands.length}`);
recentCommands.slice(0, 5).forEach((cmd, i) => {
console.log(` ${i + 1}. ${cmd}`);
});
if (recentCommands.length > 5) {
console.log(` ... and ${recentCommands.length - 5} more`);
}
}
console.log(` File: ${CONFIG_FILES.recentCommands}`);
console.log('');
// Filter state
console.log('Filter State:');
const filterState = loadFilterState();
if (!filterState) {
console.log(' No saved filter state');
} else {
console.log(' Saved filters:');
for (const [key, value] of Object.entries(filterState)) {
console.log(` ${key}: ${value}`);
}
}
console.log(` File: ${CONFIG_FILES.filterState}`);
}
/**
* Load current theme from config
*/
function loadTheme(): string {
try {
if (fs.existsSync(CONFIG_FILES.theme)) {
const content = fs.readFileSync(CONFIG_FILES.theme, 'utf-8');
const config = JSON.parse(content);
return config.theme || 'dark';
}
} catch {
// Ignore errors
}
return 'dark';
}
/**
* Set theme in config
*/
function setTheme(theme: string): void {
if (theme !== 'dark' && theme !== 'light') {
console.error(`Invalid theme: ${theme}. Must be 'dark' or 'light'.`);
process.exit(1);
}
try {
// Ensure config directory exists
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
const config = { theme };
fs.writeFileSync(CONFIG_FILES.theme, JSON.stringify(config, null, 2), 'utf-8');
console.log(`Theme set to: ${theme}`);
} catch (err) {
console.error(`Failed to set theme: ${(err as Error).message}`);
process.exit(1);
}
}
/**
* Load recent commands from config
*/
function loadRecentCommands(): string[] {
try {
if (fs.existsSync(CONFIG_FILES.recentCommands)) {
const data = fs.readFileSync(CONFIG_FILES.recentCommands, 'utf-8');
return JSON.parse(data);
}
} catch {
// Ignore errors
}
return [];
}
/**
* Load filter state from config
*/
function loadFilterState(): Record<string, unknown> | null {
try {
if (fs.existsSync(CONFIG_FILES.filterState)) {
const data = fs.readFileSync(CONFIG_FILES.filterState, 'utf-8');
return JSON.parse(data);
}
} catch {
// Ignore errors
}
return null;
}
/**
* List focus presets
*/
function listPresets(): void {
const presetManager = createTuiPresetManager();
const presets = presetManager.getPresets();
console.log('Focus Presets');
console.log('=============');
if (presets.length === 0) {
console.log('No presets saved.');
console.log('');
console.log('Create a preset from the TUI by:');
console.log(' 1. Pin workers and beads in focus mode');
console.log(' 2. Open command palette (Ctrl+P)');
console.log(' 3. Select "Save current view as preset"');
return;
}
presets.forEach(preset => {
const createdAt = new Date(preset.createdAt).toLocaleString();
console.log('');
console.log(`Name: ${preset.name}`);
console.log(` Workers: ${preset.pinnedWorkers.length > 0 ? preset.pinnedWorkers.join(', ') : 'none'}`);
console.log(` Beads: ${preset.pinnedBeads.length > 0 ? preset.pinnedBeads.join(', ') : 'none'}`);
console.log(` Created: ${createdAt}`);
if (preset.description) {
console.log(` Description: ${preset.description}`);
}
});
}
/**
* Delete a focus preset
*/
function deletePreset(name: string): void {
const presetManager = createTuiPresetManager();
if (!presetManager.hasPreset(name)) {
console.error(`Preset not found: ${name}`);
console.log('');
console.log('Available presets:');
const presets = presetManager.getPresetNames();
if (presets.length === 0) {
console.log(' (none)');
} else {
presets.forEach(p => console.log(` - ${p}`));
}
process.exit(1);
}
if (presetManager.deletePreset(name)) {
console.log(`Deleted preset: ${name}`);
} else {
console.error(`Failed to delete preset: ${name}`);
process.exit(1);
}
}
/**
* Clear all config state
*/
function clearConfig(options: { theme?: boolean; presets?: boolean; commands?: boolean; filters?: boolean; all?: boolean }): void {
const filesToDelete: string[] = [];
if (options.all || options.theme) {
filesToDelete.push(CONFIG_FILES.theme);
}
if (options.all || options.presets) {
filesToDelete.push(CONFIG_FILES.presets);
}
if (options.all || options.commands) {
filesToDelete.push(CONFIG_FILES.recentCommands);
}
if (options.all || options.filters) {
filesToDelete.push(CONFIG_FILES.filterState);
}
if (filesToDelete.length === 0) {
console.log('Nothing to clear. Use --all or specify what to clear.');
console.log('');
console.log('Options:');
console.log(' --theme Clear theme preference');
console.log(' --presets Clear focus presets');
console.log(' --commands Clear recent commands');
console.log(' --filters Clear filter state');
console.log(' --all Clear all config');
return;
}
let deletedCount = 0;
for (const file of filesToDelete) {
try {
if (fs.existsSync(file)) {
fs.unlinkSync(file);
console.log(`Deleted: ${file}`);
deletedCount++;
} else {
console.log(`Not found (skipped): ${file}`);
}
} catch (err) {
console.error(`Failed to delete ${file}: ${(err as Error).message}`);
}
}
if (deletedCount === 0) {
console.log('');
console.log('No files were deleted.');
} else {
console.log('');
console.log(`Deleted ${deletedCount} config file(s).`);
}
}
/**
* Create the config command
*/
export function createConfigCommand(): Command {
const cmd = new Command('config')
.description('Manage FABRIC configuration')
.action(() => {
showConfig();
});
// Theme subcommand
cmd.command('theme')
.description('Show or set theme')
.argument('[theme]', 'Theme to set (dark or light)')
.action((theme?: string) => {
if (theme) {
setTheme(theme);
} else {
console.log(`Current theme: ${loadTheme()}`);
}
});
// Presets subcommand
const presetsCmd = cmd.command('presets')
.description('Manage focus presets');
presetsCmd.command('list')
.alias('ls')
.description('List all focus presets')
.action(() => {
listPresets();
});
presetsCmd.command('delete')
.alias('rm')
.description('Delete a focus preset')
.argument('<name>', 'Preset name to delete')
.action((name: string) => {
deletePreset(name);
});
// Clear subcommand
cmd.command('clear')
.description('Clear configuration state')
.option('--theme', 'Clear theme preference')
.option('--presets', 'Clear focus presets')
.option('--commands', 'Clear recent commands')
.option('--filters', 'Clear filter state')
.option('--all', 'Clear all config')
.action((options) => {
clearConfig(options);
});
return cmd;
}