Markdown Converter
Agent skill for markdown-converter
A CLI-based tmux session manager built with TypeScript, React, and Ink. This tool manages development sessions for feature branches across multiple projects, integrating with git worktrees and Claude AI.
Sign in to like and favorite skills
A CLI-based tmux session manager built with TypeScript, React, and Ink. This tool manages development sessions for feature branches across multiple projects, integrating with git worktrees and Claude AI.
Git worktrees allow multiple branches to be checked out simultaneously in different directories. This app manages worktrees in a structured way:
{projects-directory}/{project-name}/{projects-directory}/{project-name}-branches/{feature-name}/{projects-directory}/{project-name}-archived/archived-{timestamp}_{feature-name}/The projects directory is configurable:
devteam --dir /path/to/projectsPROJECTS_DIR=/path/to/projects devteamEach worktree gets associated tmux sessions:
dev-{project}-{feature} (for Claude AI)dev-{project}-{feature}-shell (for terminal work)dev-{project}-{feature}-run (for executing commands)The app monitors Claude AI status in tmux panes:
src/ ├── index.ts # Entry point ├── bootstrap.tsx # App bootstrap (Ink render) ├── App.tsx # Main React component ├── bin/ # CLI binary │ └── devteam.ts # CLI executable ├── components/ # React/Ink UI components │ ├── common/ # Shared components │ ├── dialogs/ # Modal dialogs │ └── views/ # Main views ├── contexts/ # React contexts (State + Operations) │ ├── WorktreeContext.tsx # Worktree state and operations │ ├── GitHubContext.tsx # PR status and GitHub operations │ └── UIContext.tsx # UI navigation and dialog state ├── hooks/ # React hooks │ ├── useKeyboardShortcuts.ts # Keyboard handling │ ├── usePRStatus.ts # PR status fetching │ └── useWorktrees.ts # Worktree management ├── screens/ # Full-screen components │ ├── WorktreeListScreen.tsx # Main list view │ ├── CreateFeatureScreen.tsx # Feature creation │ ├── ArchiveConfirmScreen.tsx # Archive confirmation │ └── ArchivedScreen.tsx # Archived items view ├── services/ # Stateless data operations │ ├── GitService.ts # Local git operations │ ├── GitHubService.ts # GitHub API operations │ ├── TmuxService.ts # Tmux session management │ └── WorktreeService.ts # Git + Tmux orchestration ├── shared/utils/ # Utility functions │ ├── commandExecutor.ts # Process execution │ ├── fileSystem.ts # File operations │ ├── formatting.ts # String formatting │ └── gitHelpers.ts # Git utilities ├── models.ts # Data models/classes ├── constants.ts # App constants └── ops.ts # Complex operations tests/ ├── fakes/ # Fake service implementations │ ├── FakeGitService.ts # In-memory git │ ├── FakeTmuxService.ts # In-memory tmux │ ├── FakeWorktreeService.ts # In-memory worktree │ └── stores.ts # Memory data stores ├── utils/ # Test utilities │ ├── renderApp.tsx # Test app rendering │ └── testHelpers.ts # Setup helpers ├── unit/ # Unit tests └── e2e/ # End-to-end tests
Import Style: Use ESM imports with
.js extension (even for TS files)
import {GitService} from '../services/GitService.js';
JSX Syntax: Use modern JSX syntax for React components
return ( <Box flexDirection="column"> <Text>Hello</Text> </Box> );
File Extensions:
.ts for non-React files (services, utils, models).tsx for React components and contextsClass Models: Use classes with constructor initialization
export class WorktreeInfo { project: string; feature: string; constructor(init: Partial<WorktreeInfo> = {}) { this.project = ''; this.feature = ''; Object.assign(this, init); } }
Service Pattern: Services are classes with dependency injection
export class WorktreeService { constructor( private gitService?: GitService, private tmuxService?: TmuxService ) { this.gitService = gitService || new GitService(); this.tmuxService = tmuxService || new TmuxService(); } }
Context Providers: Use React Context for state + operations
export function WorktreeProvider({children}) { const [worktrees, setWorktrees] = useState([]); const gitService = new GitService(); const tmuxService = new TmuxService(); const createFeature = async (project, name) => { await gitService.createWorktree(project, name); await tmuxService.createSession(project, name); refresh(); }; const value = { worktrees, createFeature, refresh }; return ( <WorktreeContext.Provider value={value}> {children} </WorktreeContext.Provider> ); }
.ts, PascalCase for .tsxWorktreeListScreen)use prefix (e.g., useWorktrees)Service suffixInfo or State suffixService Layer (Stateless Data Operations):
Context Layer (State Management + Operations):
Component Layer: Thin components that use contexts
Unit Tests (
tests/unit/): Test services and state logic in isolation
test('should create worktree', () => { const gitService = new FakeGitService(); const result = gitService.createWorktree('project', 'feature'); expect(result).toBe(true); });
E2E Tests (
tests/e2e/): Full user workflows and cross-service interactions
test('complete feature workflow', async () => { const {result, stdin} = renderApp(); stdin.write('n'); // Create new await delay(100); stdin.write('\r'); // Select project // ... continue workflow });
tests/e2e/ (mock-rendered):
tests/utils/renderApp.tsx (mock output driver) for deterministic frames.tests/e2e/terminal/ (terminal-oriented, Node runner):
npm run test:terminal — builds the project, compiles fakes, and runs terminal checks:
tests/e2e/terminal/run-smoke.mjs: Ink tests/e2e/terminal/run-mainview-list.mjs: MainView rows rendertests/e2e/terminal/run-app-full.mjs: Full App providers render and list rows appeardist/ and dist-tests; the script runs both builds.npm test # Run all Jest tests (unit + E2E) npm run test:watch # Jest watch mode npm run typecheck # Type checking only npm run test:terminal # Run terminal rendering tests (Node runner)
Create in
src/components/dialogs/:
import React from 'react'; import {Box, Text} from 'ink'; interface MyDialogProps { title: string; onClose: () => void; } export default function MyDialog({title, onClose}: MyDialogProps) { return ( <Box flexDirection="column"> <Text>{title}</Text> {/* Dialog content */} </Box> ); }
Create in
src/services/:
export class MyService { fetchData(params: any): Promise<DataType[]> { // Fetch and transform data only - no state return runCommand(['some-command', params]); } transformData(raw: any): DataType { // Pure transformation functions return new DataType(raw); } }
Create in
src/contexts/:
export function MyContextProvider({children}) { const [data, setData] = useState([]); const [loading, setLoading] = useState(false); const myService = new MyService(); const loadData = async () => { setLoading(true); const result = await myService.fetchData(); setData(result); setLoading(false); }; const createItem = async (item) => { await myService.createItem(item); loadData(); // Refresh state }; const value = { data, loading, loadData, createItem }; return h(MyContext.Provider, {value}, children); }
Create in
src/screens/:
import React from 'react'; import {Box} from 'ink'; import {useMyContext} from '../contexts/MyContext.js'; import {useUIContext} from '../contexts/UIContext.js'; export default function MyScreen() { const {data, loading, createItem} = useMyContext(); const {showList} = useUIContext(); return ( <Box> {/* Screen content that uses context state and operations */} </Box> ); }
For multi-step operations, use
src/ops.ts:
export async function complexOperation( services: Services, params: OperationParams ): Promise<Result> { // Step 1: Validate // Step 2: Execute // Step 3: Update state return result; }
The app copies these files to worktrees:
.env.local - Environment variables.claude/settings.local.json - Claude settingsCLAUDE.md - Claude documentationRefresh Rates: Different refresh intervals for different data:
Pagination: List views paginate at 20 items by default
Memory Management: Fake services clear old data periodically
try { const result = runCommand(['git', 'status']); return result; } catch (error) { // Silent fail for UI operations return null; }
const loadData = async () => { const data = await fetchPRStatus(); setState(prev => ({...prev, prStatus: data})); };
useKeyboardShortcuts({ onMove: (delta) => moveSelection(delta), onSelect: () => handleSelect(), onCreate: () => setMode('create') });
The app includes comprehensive file-based logging for all console output and errors:
./logs/ ├── errors.log # Error messages and stack traces └── console.log # All console output (log, warn, info, debug)
Automatic Console Logging: All
console.log, console.error, console.warn, console.info, and console.debug calls are automatically logged to files when initializeFileLogging() is called.
Manual Logging Functions: Use these functions for structured logging:
import {logError, logInfo, logWarn, logDebug} from '../shared/utils/logger.js'; logError('Database connection failed', error); logInfo('User created successfully', {userId: 123}); logWarn('API rate limit approaching', {remaining: 10}); logDebug('Cache hit', {key: 'user:123'});
Log Management:
import {getLogPaths, clearLogs} from '../shared/utils/logger.js'; // Get log file paths const {errorLog, consoleLog} = getLogPaths(); // Clear all logs clearLogs();
Each log entry includes:
Example:
[2025-08-27T10:30:45.123Z] ERROR: Database connection failed {"host":"localhost","port":5432} [2025-08-27T10:30:46.456Z] INFO: User login successful {"userId":123,"email":"[email protected]"}
errors.log.1724765445123./logs/ for detailed error traces and debug infoconsole.error() (stdout is used by Ink)tmux lstail -f ./logs/errors.log to monitor errors in real-timeError Logging: Always log errors with context:
try { await createWorktree(project, feature); } catch (error) { logError('Failed to create worktree', {project, feature, error}); throw error; }
Debug Information: Log debug info for complex operations:
logDebug('Starting worktree creation', {project, feature, targetPath});
Performance Monitoring: Log timing for slow operations:
const start = Date.now(); await longOperation(); logInfo('Operation completed', {duration: Date.now() - start});
npm run build # Compile TypeScript npm run typecheck # Check types only npm link # Install globally as 'devteam'
When adding features: