NOTE: The project is currently changing rapidly, there's a high likelihood that PRs will be out of sync with latest code versions and may be hard to rebase.
Thank you for your interest in contributing to Maestro! This document provides guidelines, setup instructions, and practical guidance for developers.
For architecture details, see ARCHITECTURE.md. For quick reference while coding, see CLAUDE.md.
Snappy interface and reduced battery consumption are fundamental goals for Maestro. Every contribution should consider:
- Responsiveness: UI interactions should feel instant. Avoid blocking the main thread.
- Battery efficiency: Minimize unnecessary timers, polling, and re-renders.
- Memory efficiency: Clean up event listeners, timers, and subscriptions properly.
See Performance Guidelines for specific practices.
- Development Setup
- Project Structure
- Development Scripts
- Testing
- Linting & Pre-commit Hooks
- Common Development Tasks
- Encore Features (Feature Gating)
- Adding a New AI Agent
- Code Style
- Performance Guidelines
- Debugging Guide
- Commit Messages
- Pull Request Process (includes automated code review)
- Building for Release
- Documentation
- Node.js 20+
- npm or yarn
- Git
# Fork and clone the repository
git clone <your-fork-url>
cd maestro
# Install dependencies
npm install
# Run in development mode with hot reload
npm run devmaestro/
├── src/
│ ├── main/ # Electron main process (Node.js backend)
│ │ ├── index.ts # Entry point, IPC handlers
│ │ ├── process-manager.ts
│ │ ├── preload.ts # Secure IPC bridge
│ │ └── utils/ # Shared utilities
│ ├── renderer/ # React frontend (Desktop UI)
│ │ ├── App.tsx # Main coordinator
│ │ ├── components/ # React components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── services/ # IPC wrappers (git, process)
│ │ ├── contexts/ # React contexts
│ │ ├── constants/ # Themes, shortcuts, priorities
│ │ ├── types/ # TypeScript definitions
│ │ └── utils/ # Frontend utilities
│ ├── cli/ # CLI tool (maestro-cli)
│ │ ├── index.ts # CLI entry point
│ │ ├── commands/ # Command implementations
│ │ ├── services/ # CLI services (storage, batch processor)
│ │ └── output/ # Output formatters (human, JSONL)
│ ├── shared/ # Shared code across processes
│ │ ├── theme-types.ts # Theme type definitions
│ │ └── templateVariables.ts # Template variable system
│ └── web/ # Web interface (Remote Control)
│ └── ... # Mobile-optimized React app
├── docs/ # Mintlify documentation (hosted at docs.runmaestro.ai)
│ ├── docs.json # Mintlify configuration and navigation
│ ├── screenshots/ # All documentation screenshots
│ ├── assets/ # Logos, icons, and static assets
│ └── *.md # Documentation pages
├── build/ # Application icons
├── .github/workflows/ # CI/CD automation
└── dist/ # Build output (generated)
npm run dev # Start dev server with hot reload (isolated data directory)
npm run dev:prod-data # Start dev server using production data (requires closing production app)
npm run dev:demo # Start in demo mode (fresh settings, isolated data)
npm run dev:web # Start web interface dev server
npm run build # Full production build (main + renderer + web + CLI)
npm run build:main # Build main process only
npm run build:renderer # Build renderer only
npm run build:web # Build web interface only
npm run build:cli # Build CLI tool only
npm start # Start built application
npm run clean # Clean build artifacts
npm run lint # Run TypeScript type checking
npm run package # Package for all platforms
npm run package:mac # Package for macOS
npm run package:win # Package for Windows
npm run package:linux # Package for LinuxBy default, npm run dev uses an isolated data directory (~/Library/Application Support/maestro-dev/) separate from production. This allows you to run both dev and production instances simultaneously—useful when using the production Maestro to work on the dev instance.
| Command | Data Directory | Can Run Alongside Production? |
|---|---|---|
npm run dev |
maestro-dev/ |
✅ Yes |
npm run dev:prod-data |
maestro/ (production) |
❌ No - close production first |
npm run dev:demo |
/tmp/maestro-demo/ |
✅ Yes |
When to use each:
npm run dev— Default for most development. Start fresh or use dev-specific test data.npm run dev:prod-data— Test with your real sessions and settings. Must close production app first to avoid database lock conflicts.npm run dev:demo— Screenshots, demos, or testing with completely fresh state.
Use demo mode to run Maestro with a fresh, isolated data directory - useful for demos, testing, or screenshots without affecting your real settings:
npm run dev:demoDemo mode stores all data in /tmp/maestro-demo. For a completely fresh start each time:
rm -rf /tmp/maestro-demo && npm run dev:demoYou can also specify a custom demo directory via environment variable:
MAESTRO_DEMO_DIR=~/Desktop/my-demo npm run devWhen working with multiple git worktrees, you can run Maestro instances in parallel by specifying different ports using the VITE_PORT environment variable:
# In the main worktree (uses default port 5173)
npm run dev
# In worktree 2 (in another directory and terminal)
VITE_PORT=5174 npm run dev
# In worktree 3
VITE_PORT=5175 npm run devThis allows you to develop and test different branches simultaneously without port conflicts.
Note: The web interface dev server (npm run dev:web) uses a separate port (default 5174) and can be configured with VITE_WEB_PORT if needed.
Run the test suite with Jest:
npm test # Run all tests
npm test -- --watch # Watch mode (re-runs on file changes)
npm test -- --testPathPattern="name" # Run tests matching a pattern
npm test -- --coverage # Run with coverage reportWatch mode keeps Jest running and automatically re-runs tests when you save changes:
- Watches source and test files for changes
- Re-runs only tests affected by changed files
- Provides instant feedback during development
Interactive options in watch mode:
a- Run all testsf- Run only failing testsp- Filter by filename patternt- Filter by test name patternq- Quit watch mode
Tests are located in src/__tests__/ and organized by area:
src/__tests__/
├── cli/ # CLI tool tests
├── main/ # Electron main process tests
├── renderer/ # React component and hook tests
├── shared/ # Shared utility tests
└── web/ # Web interface tests
This project uses Husky and lint-staged to automatically format and lint staged files before each commit.
How it works:
- When you run
git commit, Husky triggers the pre-commit hook - lint-staged runs Prettier and ESLint only on your staged files
- If there are unfixable errors, the commit is blocked
- Fixed files are automatically re-staged
Setup is automatic — hooks are installed when you run npm install (via the prepare script).
Bypassing hooks (emergency only):
git commit --no-verify -m "emergency fix"Running lint-staged manually:
npx lint-stagedTroubleshooting:
- Hooks not running — Check if
.husky/pre-commithas executable permissions:chmod +x .husky/pre-commit - Wrong tool version — Ensure
npxis using localnode_modules: deletenode_modulesand runnpm install - Hook fails in CI/Docker — The
preparescript useshusky || trueto gracefully skip in environments without.git
Run TypeScript type checking and ESLint to catch errors before building:
npm run lint # TypeScript type checking (all configs: renderer, main, cli)
npm run lint:eslint # ESLint code quality checks (React hooks, unused vars, etc.)
npm run lint:eslint -- --fix # Auto-fix ESLint issues where possibleThe TypeScript linter checks all three build configurations:
tsconfig.lint.json- Renderer, web, and shared codetsconfig.main.json- Main process codetsconfig.cli.json- CLI tooling
ESLint is configured with TypeScript and React plugins (eslint.config.mjs):
react-hooks/rules-of-hooks- Enforces React hooks rulesreact-hooks/exhaustive-deps- Enforces correct hook dependencies@typescript-eslint/no-unused-vars- Warns about unused variablesprefer-const- Suggests const for never-reassigned variables
When to run manual linting:
- Pre-commit hooks handle staged files automatically
- Run full lint after significant refactors:
npm run lint && npm run lint:eslint - When CI fails with type errors
Common lint issues:
- Unused imports or variables
- Type mismatches in function calls
- Missing required properties on interfaces
- React hooks called conditionally (must be called in same order every render)
- Missing dependencies in useEffect/useCallback/useMemo
- Plan the state - Determine if it's per-agent or global
- Add state management - In
useSettings.ts(global) or agent state - Create persistence - Use wrapper function pattern for global settings
- Implement UI - Follow Tailwind + theme color pattern
- Add keyboard shortcuts - In
shortcuts.tsandApp.tsx - Test focus flow - Ensure Escape key navigation works
- Create component in
src/renderer/components/ - Add priority in
src/renderer/constants/modalPriorities.ts:MY_MODAL: 600,
- Register with layer stack (see ARCHITECTURE.md)
- Use proper ARIA attributes:
<div role="dialog" aria-modal="true" aria-label="My Modal">
-
Add definition in
src/renderer/constants/shortcuts.ts:myShortcut: { id: 'myShortcut', label: 'My Action', keys: ['Meta', 'k'] },
-
Add handler in
App.tsxkeyboard event listener:else if (isShortcut(e, 'myShortcut')) { e.preventDefault(); // Handler code }
Supported modifiers: Meta (Cmd/Win), Ctrl, Alt, Shift
Arrow keys: ArrowLeft, ArrowRight, ArrowUp, ArrowDown
-
Add state in
useSettings.ts:const [mySetting, setMySettingState] = useState(defaultValue);
-
Create wrapper function:
const setMySetting = (value) => { setMySettingState(value); window.maestro.settings.set('mySetting', value); };
-
Load in useEffect:
const saved = await window.maestro.settings.get('mySetting'); if (saved !== undefined) setMySettingState(saved);
-
Add to return object and export.
Slash commands are now Custom AI Commands defined in Settings, not in code. They are prompt macros that get substituted and sent to the AI agent.
To add a built-in slash command that users see by default, add it to the Custom AI Commands default list in useSettings.ts. Each command needs:
{
command: '/mycommand',
description: 'Does something useful',
prompt: 'The prompt text with {{TEMPLATE_VARIABLES}}',
}For commands that need programmatic behavior (not just prompts), handle them in App.tsx where slash commands are processed before being sent to the agent.
Maestro bundles two spec-driven workflow systems. To add a similar bundled command set:
- Create prompts directory:
src/prompts/my-workflow/ - Add command markdown files:
my-workflow.command1.md,my-workflow.command2.md - Create index.ts: Export command definitions with IDs, slash commands, descriptions, and prompts
- Create metadata.json: Track source version, commit SHA, and last refreshed date
- Create manager:
src/main/my-workflow-manager.ts(handles loading, saving, refreshing) - Add IPC handlers: In
src/main/index.tsfor get/set/refresh operations - Add preload API: In
src/main/preload.tsto expose to renderer - Create UI panel: Similar to
OpenSpecCommandsPanel.tsxorSpecKitCommandsPanel.tsx - Add to extraResources: In
package.jsonbuild config for all platforms - Create refresh script:
scripts/refresh-my-workflow.mjs
Reference the existing Spec-Kit (src/prompts/speckit/, src/main/speckit-manager.ts) and OpenSpec (src/prompts/openspec/, src/main/openspec-manager.ts) implementations.
Maestro has 16 themes across 3 modes: dark, light, and vibe.
Add to src/renderer/constants/themes.ts:
'my-theme': {
id: 'my-theme',
name: 'My Theme',
mode: 'dark', // 'dark', 'light', or 'vibe'
colors: {
bgMain: '#...', // Main background
bgSidebar: '#...', // Sidebar background
bgActivity: '#...', // Activity/hover background
border: '#...', // Border color
textMain: '#...', // Primary text
textDim: '#...', // Secondary/dimmed text
accent: '#...', // Accent color
accentDim: 'rgba(...)', // Dimmed accent (with alpha)
accentText: '#...', // Text in accent contexts
accentForeground: '#...', // Text ON accent backgrounds (contrast)
success: '#...', // Success state (green)
warning: '#...', // Warning state (yellow/orange)
error: '#...', // Error state (red)
}
}Then add the ID to ThemeId type in src/shared/theme-types.ts and to the isValidThemeId function.
-
Add handler in
src/main/index.ts:ipcMain.handle('myNamespace:myAction', async (_, arg1, arg2) => { // Implementation return result; });
-
Expose in
src/main/preload.ts:myNamespace: { myAction: (arg1, arg2) => ipcRenderer.invoke('myNamespace:myAction', arg1, arg2), },
-
Add types to
MaestroAPIinterface in preload.ts.
Encore Features is Maestro's system for optional, user-toggled features. It serves as a precursor to a full plugin marketplace — features that are powerful but not essential for every user can be shipped as Encore Features, disabled by default.
Consider making your feature an Encore Feature when:
- It adds significant UI surface area (new modals, panels, shortcuts) that not all users need
- It integrates with external services or has resource overhead
- It's experimental or targeting a niche workflow
- It would clutter the interface for users who don't want it
When disabled, an Encore Feature must be completely invisible — no keyboard shortcuts, no menu items, no command palette entries.
Encore Features are managed through a single settings object:
// src/renderer/types/index.ts
export interface EncoreFeatureFlags {
directorNotes: boolean;
// Add new features here
}The flags live in useSettings.ts and persist via window.maestro.settings. The Encore Features panel in Settings (SettingsModal.tsx) provides toggle UI for each feature.
-
Add the flag to
EncoreFeatureFlagsinsrc/renderer/types/index.ts:export interface EncoreFeatureFlags { directorNotes: boolean; myFeature: boolean; // Add here }
-
Set the default in
useSettings.ts— always default tofalse:const DEFAULT_ENCORE_FEATURES: EncoreFeatureFlags = { directorNotes: false, myFeature: false, };
-
Add toggle UI in
SettingsModal.tsxunder the Encore Features tab. Follow the existing Director's Notes pattern — a clickable section with a toggle switch and feature-specific settings that only render when enabled. -
Gate all access points — the feature must be invisible when disabled:
- Keyboard shortcuts (
useMainKeyboardHandler.ts): Guard withctx.encoreFeatures?.myFeature - App.tsx: Conditionally pass callbacks and render modals based on
encoreFeatures.myFeature - SessionList hamburger menu: Make the setter optional and conditionally render the menu item
- Quick Actions (
QuickActionsModal.tsx): Passundefinedfor the handler when disabled
- Keyboard shortcuts (
-
Update tests in
SettingsModal.test.tsx— add toggle and settings tests within the Encore Features describe block.
| Feature | Flag | Description |
|---|---|---|
| Director's Notes | directorNotes |
AI-generated synopsis of work across sessions |
Maestro supports multiple AI coding agents. Each agent has different capabilities that determine which UI features are available. For detailed architecture, see AGENT_SUPPORT.md.
Before implementing, investigate the agent's CLI to determine which capabilities it supports:
| Capability | Question to Answer | Example |
|---|---|---|
| Session Resume | Can the provider resume a previous conversation? | --resume <id>, --session <id> |
| Read-Only Mode | Is there a plan/analysis-only mode? | --permission-mode plan, --agent plan |
| JSON Output | Does it emit structured JSON? | --output-format json, --format json |
| Session ID | Does output include a session identifier? | session_id, sessionID in JSON |
| Image Input | Can you send images to the agent? | --input-format stream-json, -f image.png |
| Slash Commands | Are there discoverable commands? | Emitted in init message |
| Session Storage | Does the provider persist sessions to disk? | ~/.agent/sessions/ |
| Cost Tracking | Is it API-based with costs? | Cloud API vs local model |
| Usage Stats | Does it report token counts? | tokens, usage in output |
| Batch Mode | Does it run per-message or persistently? | --print vs interactive |
In src/main/agent-detector.ts, add to AGENT_DEFINITIONS:
{
id: 'my-agent',
name: 'My Agent',
binaryName: 'myagent',
command: 'myagent',
args: ['--json'], // Base args for batch mode
},In src/main/agent-capabilities.ts (create if needed):
'my-agent': {
supportsResume: true, // Set based on investigation
supportsReadOnlyMode: false, // Set based on investigation
supportsJsonOutput: true,
supportsSessionId: true,
supportsImageInput: false,
supportsSlashCommands: false,
supportsSessionStorage: false,
supportsCostTracking: false, // true for API-based agents
supportsUsageStats: true,
supportsBatchMode: true,
supportsStreaming: true,
},In src/main/agent-output-parser.ts, add a parser for the agent's JSON format:
class MyAgentOutputParser implements AgentOutputParser {
parseJsonLine(line: string): ParsedEvent {
const msg = JSON.parse(line);
return {
type: msg.type,
sessionId: msg.session_id, // Agent-specific field name
text: msg.content, // Agent-specific field name
tokens: msg.usage, // Agent-specific field name
};
}
}Add argument builders for capability-driven flags:
// In agent definition
resumeArgs: (sessionId) => ['--resume', sessionId],
readOnlyArgs: ['--read-only'], // If supported
jsonOutputArgs: ['--format', 'json'],
batchModePrefix: ['run'], // If needed (e.g., 'myagent run "prompt"')If the agent persists sessions to disk:
class MyAgentSessionStorage implements AgentSessionStorage {
async listSessions(projectPath: string): Promise<AgentSession[]> {
// Read from agent's session directory
}
async readSession(projectPath: string, sessionId: string): Promise<Message[]> {
// Parse session file format
}
}# 1. Verify agent detection
npm run dev
# Check Settings → AI Agents shows your agent
# 2. Test new session
# Create session with your agent, send a message
# 3. Test JSON parsing
# Verify response appears correctly in UI
# 4. Test resume (if supported)
# Close and reopen tab, send follow-up message
# 5. Test read-only mode (if supported)
# Toggle read-only, verify agent refuses writesBased on capabilities, these UI features are automatically enabled/disabled:
| Feature | Required Capability | Component |
|---|---|---|
| Read-only toggle | supportsReadOnlyMode |
InputArea |
| Image attachment | supportsImageInput |
InputArea |
| Session browser | supportsSessionStorage |
RightPanel |
| Resume button | supportsResume |
AgentSessionsBrowser |
| Cost widget | supportsCostTracking |
MainPanel |
| Token display | supportsUsageStats |
MainPanel, TabBar |
| Session ID pill | supportsSessionId |
MainPanel |
| Slash autocomplete | supportsSlashCommands |
InputArea |
| Agent | Resume | Read-Only | JSON | Images | Sessions | Cost | Status |
|---|---|---|---|---|---|---|---|
| Claude Code | ✅ --resume |
✅ --permission-mode plan |
✅ | ✅ | ✅ ~/.claude/ |
✅ | ✅ Complete |
| Codex | ✅ exec resume |
✅ --sandbox read-only |
✅ | ✅ | ✅ ~/.codex/ |
❌ (tokens only) | ✅ Complete |
| OpenCode | ✅ --session |
✅ --agent plan |
✅ | ✅ | ✅ ~/.local/share/opencode/ |
✅ | ✅ Complete |
| Factory Droid | ✅ -s, --session-id |
✅ (default mode) | ✅ | ✅ | ✅ ~/.factory/ |
❌ (tokens only) | ✅ Complete |
| Gemini CLI | TBD | TBD | TBD | TBD | TBD | ✅ | 📋 Planned |
For detailed implementation guide, see AGENT_SUPPORT.md.
- Strict mode enabled
- Interface definitions for all data structures
- Export types via
preload.tsfor renderer
- Functional components with hooks
- Keep components focused and small
- Use Tailwind for layout, inline styles for theme colors
- Maintain keyboard accessibility
- Use
tabIndex={-1}+outline-nonefor programmatic focus
- Always use
execFileNoThrowfor external commands (never shell-based execution) - Keep context isolation enabled
- Use preload script for all IPC
- Sanitize all user inputs
- Use
spawn()withshell: false
Maestro prioritizes a snappy interface and minimal battery consumption. Follow these guidelines:
- Memoize expensive computations with
useMemo- especially sorting, filtering, and transformations - Use Maps for lookups instead of
Array.find()in loops (O(1) vs O(n)) - Batch state updates - use the
useBatchedSessionUpdateshook for high-frequency IPC updates - Avoid creating objects/arrays in render - move static objects outside components or memoize them
// Bad: O(n) lookup in every iteration
agents.filter((a) => {
const group = groups.find((g) => g.id === a.groupId); // O(n) per agent
return group && !group.collapsed;
});
// Good: O(1) lookup with memoized Map
const groupsById = useMemo(() => new Map(groups.map((g) => [g.id, g])), [groups]);
agents.filter((a) => {
const group = groupsById.get(a.groupId); // O(1)
return group && !group.collapsed;
});- Prefer longer intervals - 3 seconds instead of 1 second for non-critical updates
- Use
setTimeoutsparingly - consider if the delay is truly necessary - Clean up all timers in
useEffectcleanup functions - Avoid polling - use event-driven updates via IPC when possible
// RightPanel.tsx uses 3-second intervals for elapsed time updates
intervalRef.current = setInterval(updateElapsed, 3000); // Not 1000ms- Remove event listeners in cleanup functions
- Clear Maps and Sets when no longer needed
- Use WeakMap/WeakSet for caches that should allow garbage collection
- Limit log buffer sizes - truncate old entries when buffers grow large
- Batch IPC calls - combine multiple small calls into fewer larger ones
- Debounce persistence - use
useDebouncedPersistencefor settings that change frequently - Stream large data - don't load entire files into memory when streaming is possible
React DevTools (Standalone): For profiling React renders and inspecting component trees:
# Install globally (once)
npm install -g react-devtools
# Launch the standalone app
npx react-devtoolsThen run npm run dev — the app auto-connects (connection script in src/renderer/index.html).
Tabs:
- Components — Inspect React component tree, props, state, hooks
- Profiler — Record and analyze render performance, identify unnecessary re-renders
Profiler workflow:
- Click the record button (blue circle)
- Interact with the app (navigate, type, scroll)
- Stop recording
- Analyze the flame graph for:
- Components that render too often
- Render times per component
- Why a component rendered (props/state/hooks changed)
Chrome DevTools Performance tab (Cmd+Option+I → Performance):
- Record during the slow operation
- Look for long tasks (>50ms) blocking the main thread
- Identify expensive JavaScript execution or layout thrashing
- Add
tabIndex={0}ortabIndex={-1}to element - Add
outline-noneclass to hide focus ring - Use
ref={(el) => el?.focus()}for auto-focus - Check for
e.stopPropagation()blocking events
- Ensure wrapper function calls
window.maestro.settings.set() - Check loading code in
useSettings.tsuseEffect - Verify the key name matches in both save and load
- Register modal with layer stack (don't handle Escape locally)
- Check priority in
modalPriorities.ts - Use ref pattern to avoid re-registration:
const onCloseRef = useRef(onClose); onCloseRef.current = onClose;
- Use
style={{ color: theme.colors.textMain }}instead of Tailwind color classes - Check theme prop is passed to component
- Never use hardcoded hex colors for themed elements
- Check agent ID matches (with
-aior-terminalsuffix) - Verify
onDatalistener is registered - Check process spawned successfully (check pid > 0)
- Look for errors in DevTools console
Electron DevTools: Open via Quick Actions (Cmd+K → "Toggle DevTools") or set DEBUG=true env var.
Use conventional commits:
feat: new feature
fix: bug fix
docs: documentation changes
refactor: code refactoring
test: test additions/changes
chore: build process or tooling changes
Example: feat: add context usage visualization
PRs are automatically reviewed by two AI-powered tools:
CodeRabbit — Line-level code review. When you open or update a PR, CodeRabbit will:
- Post a PR summary with a walkthrough of changes
- Leave inline review comments on potential issues
- Provide a sequence diagram for complex changes
| Command | Effect |
|---|---|
@coderabbitai review |
Trigger a full review (useful for existing PRs) |
@coderabbitai summary |
Regenerate the PR summary |
@coderabbitai resolve |
Resolve all CodeRabbit review comments |
@coderabbitai configuration |
Show current repo settings |
You can reply to any CodeRabbit comment to ask follow-up questions — it responds conversationally.
Greptile — Codebase-aware review with deeper architectural context. Greptile indexes the full repo and reviews PRs with understanding of how changes relate to the broader codebase.
| Command | Effect |
|---|---|
@greptile |
Ask Greptile a question or request a review in any PR comment |
Reply to Greptile comments the same way you would CodeRabbit.
All PRs must pass these checks before review:
-
Linting passes — Run both TypeScript and ESLint checks:
npm run lint # TypeScript type checking npm run lint:eslint # ESLint code quality
-
Tests pass — Run the full test suite:
npm test -
Manual testing — Test affected features in the running app:
npm run dev
Verify that:
- Your feature works as expected
- Related features still work (keyboard shortcuts, focus flow, themes)
- No console errors in DevTools (
Cmd+Option+I) - UI renders correctly across different themes (try at least one dark and one light)
- Linting passes (
npm run lint && npm run lint:eslint) - Tests pass (
npm test) - Manually tested affected features
- No new console warnings or errors
- Documentation updated if needed (code comments, README, or
docs/) - Commit messages follow conventional format
- Create a feature branch from
main - Make your changes following the code style
- Complete the checklist above
- Push and open a PR with a clear description:
- What the change does
- Why it's needed
- How to test it
- Screenshots for UI changes
- CodeRabbit will automatically review your PR
- Address any CodeRabbit and maintainer feedback
Before releasing, check if the upstream spec-kit and OpenSpec repositories have updates:
# Refresh GitHub's spec-kit prompts
npm run refresh-speckit
# Refresh Fission-AI's OpenSpec prompts
npm run refresh-openspecThese scripts fetch the latest prompts from their respective repositories:
- Spec-Kit: github/spec-kit →
src/prompts/speckit/ - OpenSpec: Fission-AI/OpenSpec →
src/prompts/openspec/
Custom Maestro-specific prompts (/speckit.implement, /openspec.implement, /openspec.help) are never overwritten by the refresh scripts.
Review any changes with git diff before committing.
Place icons in build/ directory:
icon.icns- macOS (512x512 or 1024x1024)icon.ico- Windows (256x256)icon.png- Linux (512x512)
Update in package.json:
{
"version": "0.1.0"
}npm run package # All platforms
npm run package:mac # macOS (.dmg, .zip)
npm run package:win # Windows (.exe)
npm run package:linux # Linux (.AppImage, .deb, .rpm)Output in release/ directory.
Create a release tag to trigger automated builds:
git tag v0.1.0
git push origin v0.1.0GitHub Actions will build for all platforms and create a release.
User documentation is hosted on Mintlify at docs.runmaestro.ai. The source files live in the docs/ directory.
docs/
├── docs.json # Mintlify configuration (navigation, theme, links)
├── index.md # Homepage
├── screenshots/ # All documentation screenshots (PNG format)
├── assets/ # Logos, icons, favicons
├── about/ # Overview and background pages
│ └── overview.md
└── *.md # Feature and reference pages
Pages are organized by topic in docs.json under navigation.dropdowns:
| Group | Pages | Purpose |
|---|---|---|
| Overview | index, about/overview, features, screenshots | Introduction and feature highlights |
| Getting Started | installation, getting-started | Onboarding new users |
| Usage | general-usage, history, context-management, autorun-playbooks, git-worktrees, group-chat, remote-access, slash-commands, speckit-commands, configuration | Feature documentation |
| Providers & CLI | provider-notes, cli | Provider configuration and command line docs |
| Reference | achievements, keyboard-shortcuts, troubleshooting | Quick reference guides |
-
Create the markdown file in
docs/:--- title: My Feature description: A brief description for SEO and navigation. icon: star --- Content goes here...
-
Add to navigation in
docs/docs.json:{ "group": "Usage", "pages": ["existing-page", "my-feature"] } -
Reference from other pages using relative links:
See [My Feature](./my-feature) for details.
Every documentation page needs YAML frontmatter:
| Field | Required | Description |
|---|---|---|
title |
Yes | Page title (appears in navigation and browser tab) |
description |
Yes | Brief description for SEO and page previews |
icon |
No | Mintlify icon for navigation |
All screenshots are stored in docs/screenshots/ and referenced with relative paths.
Adding a new screenshot:
-
Capture the screenshot using Maestro's demo mode for clean, consistent visuals:
rm -rf /tmp/maestro-demo && npm run dev:demo -
Save as PNG in
docs/screenshots/with a descriptive kebab-case name:docs/screenshots/my-feature-overview.png docs/screenshots/my-feature-settings.png -
Reference in markdown using relative paths:

Screenshot guidelines:
- Use PNG format for UI screenshots (better quality for text)
- Capture at standard resolution (avoid Retina 2x for smaller file sizes, or use 2x for crisp details)
- Use a consistent theme (Pedurple is used in most existing screenshots)
- Crop to relevant area — don't include unnecessary whitespace or system UI
- Keep file sizes reasonable (compress if over 1MB)
Static assets like logos and icons live in docs/assets/:
| File | Usage |
|---|---|
icon.png |
Main logo (used in light and dark mode) |
icon.ico |
Favicon |
made-with-maestro.svg |
Badge for README |
maestro-app-icon.png |
High-res app icon |
Reference assets with /assets/ paths in docs.json configuration.
Documentation supports Mintlify components:
<Note>
This is an informational note.
</Note>
<Warning>
This is a warning message.
</Warning>
<Tip>
This is a helpful tip.
</Tip>Embed videos:
<iframe width="560" height="315"
src="https://www.youtube.com/embed/VIDEO_ID"
title="Video Title"
frameborder="0"
allowfullscreen>
</iframe>Tables, code blocks, and standard markdown all work as expected.
Mintlify provides a CLI for local preview. Install and run:
npm i -g mintlify
cd docs
mintlify devThis starts a local server at http://localhost:3000 with hot reload.
Maestro provides a hosted MCP (Model Context Protocol) server that allows AI applications to search the documentation:
Server URL: https://docs.runmaestro.ai/mcp
Available Tools:
SearchMaestro- Search the Maestro knowledge base for documentation, code examples, and guides
To connect from Claude Desktop or Claude Code, add to your MCP configuration:
{
"mcpServers": {
"maestro": {
"url": "https://docs.runmaestro.ai/mcp"
}
}
}See MCP Server documentation for full details.
Documentation is automatically deployed when changes to docs/ are pushed to main. Mintlify handles the build and hosting.
Open a GitHub Discussion or create an Issue.