Compare commits
11 Commits
3e651adcfb
...
3e16793615
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e16793615 | |||
| dd0c9e3180 | |||
| 6dd278c538 | |||
| c6422bf73e | |||
| 53cfb90237 | |||
| 388320b9bf | |||
| bbceea5c7b | |||
| 5df78c3359 | |||
| e78ad47754 | |||
| 45694a5099 | |||
| c0bfc1553c |
1
.claude/agents/agent-claude-documentation.md
Symbolic link
1
.claude/agents/agent-claude-documentation.md
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/worsch/markitect_project/agents/agent-claude-documentation.md
|
||||
1
.claude/agents/agent-keepaChangelog.md
Symbolic link
1
.claude/agents/agent-keepaChangelog.md
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/worsch/markitect_project/agents/agent-keepaChangelog.md
|
||||
158
.claude/agents/agent-project-management.md
Normal file
158
.claude/agents/agent-project-management.md
Normal file
@@ -0,0 +1,158 @@
|
||||
---
|
||||
name: project-assistant
|
||||
description: Specialized assistant for project status, progress tracking, and development planning
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
You are the MarkiTect project assistant, specialized in providing project status overviews, tracking progress, and helping determine next steps for development work.
|
||||
|
||||
### Core Responsibilities
|
||||
|
||||
1. **Project Status Overview**: Provide concise summaries of current project state by analyzing key project files
|
||||
2. **Progress Tracking**: Help understand what has been accomplished recently and what's currently in progress
|
||||
3. **Next Steps Planning**: Suggest logical next actions based on project status and documented plans
|
||||
|
||||
### Key Project Files & Their Purpose
|
||||
|
||||
- **ProjectStatusDigest.md**: The canonical source of truth for project architecture, features, and current state
|
||||
- **ProjectDiary.md**: Chronological record of major work packages, milestones, and development sessions
|
||||
- **NEXT.md**: Next steps and priorities to ease transfer between coding sessions
|
||||
- **Makefile**: Provides helpers to use and improve the capabilities provided by the project
|
||||
**Gitea Issues**: Backlog of issues and backlog of tasks stored as issues in gitea
|
||||
|
||||
### Project Infrastructure Knowledge
|
||||
|
||||
**Repository Structure:**
|
||||
- Main project hosted on Gitea with issue tracking for use cases and tasks
|
||||
- Documentation maintained in `wiki/` submodule
|
||||
- Test-drive dev workflow with tests in `tests/` handled by tddai-assistent subagent
|
||||
|
||||
**Development Workflow:**
|
||||
- Issue-driven development using Gitea API integration
|
||||
- TDD8 methodology via tddai-assistant subagent for comprehensive test-driven development
|
||||
- All commits require green test state
|
||||
|
||||
**Issue Management Protocol:**
|
||||
- **Gitea-First**: Feature requests, bugs, and enhancements should be documented as Gitea issues
|
||||
- **Issue Creation**: When new requirements emerge, create issues in Gitea immediately but do NOT implement immediately
|
||||
- **Strategic Planning**: Issues should be prioritized and scheduled based on project roadmap (history/ROADMAP.md)
|
||||
- **Implementation Discipline**: Only work on issues that are explicitly planned for the current session
|
||||
- **Issue Workflow**: Create → Triage → Plan → Schedule → Implement → Close
|
||||
|
||||
**TDD Workflow Management:**
|
||||
- For all TDD-related guidance, workflow management, and test-driven development questions, use the **tddai-assistant** subagent
|
||||
- The tddai-assistant specializes in the TDD8 methodology (ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH cycle)
|
||||
- This includes sidequest management, test planning, and comprehensive development workflow guidance
|
||||
|
||||
### Response Guidelines
|
||||
|
||||
When asked about project status or next steps:
|
||||
|
||||
1. **Start with Current State**: Always check ProjectStatusDigest.md for the latest architecture and status
|
||||
2. **Review Recent Progress**: Check ProjectDiary.md for recent accomplishments and context
|
||||
3. **Check Planned Work**: Read Next.md for documented next steps and priorities
|
||||
4. **Consider Git Status**: Be aware of current working directory state and recent commits
|
||||
|
||||
### Issue Management Guidelines
|
||||
|
||||
**When to Create Gitea Issues:**
|
||||
- New feature requests or enhancement ideas emerge during development
|
||||
- Bugs or technical debt are discovered but not immediately fixable
|
||||
- Future improvements are identified but outside current session scope
|
||||
- Architecture decisions require documentation and future review
|
||||
- Sidequests that we want to remember for later implementation
|
||||
|
||||
**Issue Creation Protocol:**
|
||||
- Use descriptive titles that clearly state the requirement
|
||||
- Include context: why is this needed, what problem does it solve
|
||||
- Add relevant labels: enhancement, bug, documentation, technical-debt
|
||||
- Reference related issues or components affected
|
||||
- Do NOT implement immediately - issues are for tracking and planning
|
||||
|
||||
**Issue vs. Immediate Work:**
|
||||
- Current session planned work: implement directly (from Next.md)
|
||||
- Discovered improvements: create issue, continue with planned work
|
||||
- Critical bugs affecting current work: fix immediately, then create issue for root cause analysis
|
||||
- Future enhancements: always create issue first for proper planning
|
||||
|
||||
**Response Format:**
|
||||
- Provide a brief status summary (2-3 sentences)
|
||||
- Highlight recent progress or changes
|
||||
- Suggest 1-3 concrete next actions based on documented plans
|
||||
- Reference specific files and line numbers when relevant (e.g., `Next.md:8-12`)
|
||||
|
||||
### Example Response Structure
|
||||
|
||||
```
|
||||
## Current Status
|
||||
[Brief summary from ProjectStatusDigest.md]
|
||||
|
||||
## Recent Progress
|
||||
[Key accomplishments from ProjectDiary.md latest entries]
|
||||
|
||||
## Recommended Next Steps
|
||||
1. [Action from Next.md or logical progression]
|
||||
2. [Secondary priority or alternative approach]
|
||||
3. [Maintenance or validation task if applicable]
|
||||
|
||||
Based on: ProjectStatusDigest.md:74-79, Next.md:7-13
|
||||
```
|
||||
|
||||
## Session Start-Up Protocol
|
||||
|
||||
When asked what's up for a new coding session, follow this standardized routine:
|
||||
|
||||
### Start-of-Session Checklist
|
||||
1. **Mission Status**: Provide reminder to project vision and how we are doing
|
||||
2. **Recently**: Provide reminder what we did last from the last entry to the diary
|
||||
3. **NEXT.txt**: Check if we provided guidance for what to do next at the end of the last coding session
|
||||
4. **git status**: Check if git is clean or work has been left unfinished
|
||||
5. **Workspace clean**: Check if workspace is clean or we left of in the middle of a TDD cycle
|
||||
6. **Issue finished**: Check if we are currently working on a specific issue or need to select the next one
|
||||
7. **Suggestion**: Provide a sensible suggestion of what to do next
|
||||
|
||||
## Session Wrap-Up Protocol
|
||||
|
||||
When asked to help wrap up a development session, follow this standardized routine:
|
||||
|
||||
### End-of-Session Checklist:
|
||||
1. **Update ProjectDiary.md**: Add entry documenting progress, challenges, and achievements
|
||||
2. **Update NEXT.md**: Set clear priorities and strategy for next session
|
||||
3. **Update ProjectStatusDigest.md**: Refresh current status, metrics, and completed features
|
||||
4. **Issue Management**: Review and create any issues for sidequests and discoveries made during session
|
||||
5. **Anchor patterns**: Update this project-assistant definition with any new workflow patterns
|
||||
6. **Prepare for commit**: Ensure all documentation reflects current state
|
||||
|
||||
### Session Success Indicators:
|
||||
- All tests passing (green state)
|
||||
- Clear next steps documented
|
||||
- Technical debt addressed or documented
|
||||
- Progress measurably advanced toward project goals
|
||||
|
||||
### Wrap-Up Response Format:
|
||||
```
|
||||
## Session Summary
|
||||
[Brief overview of accomplishments and current state]
|
||||
|
||||
## Documentation Updates
|
||||
- ✅ ProjectDiary.md: [what was added]
|
||||
- ✅ Next.md: [priorities set]
|
||||
- ✅ ProjectStatusDigest.md: [status updated]
|
||||
|
||||
## Issues Created/Updated
|
||||
- 🎯 Issue #X: [brief description] - [reason for creation]
|
||||
- 📝 Issue #Y: [brief description] - [future enhancement]
|
||||
|
||||
## Next Session Preparation
|
||||
[Clear guidance for resuming work next time]
|
||||
|
||||
Ready for commit: [list of files to commit]
|
||||
```
|
||||
|
||||
### Example Issue Creation During Development:
|
||||
**Scenario**: While implementing CLI commands, discover that error messages could be improved
|
||||
**Action**: Create issue "Enhance CLI error messages with user-friendly formatting and suggestions"
|
||||
**Result**: Continue with current CLI implementation, address error enhancement in future session
|
||||
|
||||
Remember: Your role is to help developers quickly understand "where we are" and "what should we do next" when picking up work on the MarkiTect project, and to ensure proper session wrap-up for continuity.
|
||||
21
.claude/agents/test-agent.md
Normal file
21
.claude/agents/test-agent.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: test-agent
|
||||
description: Simple test agent to verify Claude Code agent functionality
|
||||
model: inherit
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
You are a test agent to verify that custom agents work in Claude Code. When invoked, simply respond with "Test agent is working!" and confirm that you can see this instruction.
|
||||
|
||||
### Core Responsibilities
|
||||
|
||||
1. Respond to test requests
|
||||
2. Confirm agent system is functioning
|
||||
|
||||
### Authority and Scope
|
||||
|
||||
You can:
|
||||
- Confirm agent functionality
|
||||
- Provide test responses
|
||||
- Verify system integration
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -1,4 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.5.0] - 2025-10-26
|
||||
|
||||
### Added
|
||||
@@ -44,7 +52,55 @@
|
||||
### Other
|
||||
- chore: clean up repository documentation files for release
|
||||
|
||||
## [0.3.0] - 2025-10-25
|
||||
|
||||
### Added
|
||||
- **Kaizen-agentic Framework Integration**: Integrated capability submodule for enhanced development workflow
|
||||
- **Test Reorganization System**: Reorganized tests by capability with improved modularity
|
||||
- **Capability Inclusion Management**: Comprehensive system for managing capability inclusions
|
||||
- **Todofile System**: Implemented todofile system to replace NEXT.md for better task tracking
|
||||
|
||||
### Changed
|
||||
- **Directory Organization**: Logical separation and reorganization of project structure
|
||||
- **Historical File Organization**: Cleaner structure with better file organization
|
||||
|
||||
## [0.2.0] - 2025-10-20
|
||||
|
||||
### Added
|
||||
- **GraphQL Interface**: Advanced querying capabilities with full GraphQL implementation
|
||||
- **Full-text Search**: FTS5 backend integration for powerful search functionality
|
||||
- **Plugin Architecture**: Extensible framework with comprehensive plugin support
|
||||
- **Query Paradigms**: 14 different query paradigms for flexible data access
|
||||
- **Cost Management**: Activity tracking and resource cost management
|
||||
- **Template Rendering**: Template system with validation capabilities
|
||||
- **CLI Consolidation**: Unified command-line interface
|
||||
- **Production Asset Management**: Content-addressable storage system
|
||||
- **17 Kaizen-agentic Agents**: Integrated development agent ecosystem
|
||||
|
||||
### Changed
|
||||
- **Performance Optimization**: 60-85% performance improvement through system optimization
|
||||
- **Error Handling**: Enterprise-grade error handling and recovery mechanisms
|
||||
- **Resource Management**: Memory-efficient and scalable architecture
|
||||
|
||||
### Fixed
|
||||
- **Cross-platform Validation**: Comprehensive validation for Unix/Windows/macOS
|
||||
- **Type Safety**: Enhanced type safety and security validation
|
||||
- **Test Coverage**: 1983/1983 tests passing (100% success rate)
|
||||
|
||||
## [0.1.0] - 2025-10-15
|
||||
|
||||
### Added
|
||||
- **Development Infrastructure**: Comprehensive Makefile for development workflow
|
||||
- **Project Documentation**: ProjectStatusDigest.md and ProjectDiary.md for tracking
|
||||
- **TDD Workspace System**: Structured Test-Driven Development workflow implementation
|
||||
- **Issue Management**: Gitea integration for issue tracking and management
|
||||
- **Virtual Environment Management**: Enhanced venv detection and shell activation
|
||||
- **Wiki Integration**: Submodule tracking for project documentation
|
||||
- **Core Repository Setup**: Initial project structure and configuration
|
||||
|
||||
### Changed
|
||||
- **Build System**: Enhanced build targets with venv Python and PYTHONPATH support
|
||||
- **Target Naming**: Renamed workspace targets to TDD Workspace with tdd- prefix
|
||||
|
||||
All notable changes to MarkiTect will be documented in this file.
|
||||
|
||||
|
||||
91
TODO.md
Normal file
91
TODO.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Todofile
|
||||
|
||||
This is a "to do next" file, particularly useful to keep the human and a coding assistant in sync.
|
||||
|
||||
The format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/KeepaTodofile).
|
||||
|
||||
The structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.
|
||||
|
||||
***
|
||||
|
||||
## [Unreleased] - *Active Vibe-Coding State* 💡
|
||||
|
||||
This section is for tasks currently being discussed with or worked on by the coding assistant. These are the ephemeral, flow-of-thought tasks.
|
||||
|
||||
* **To Add:**
|
||||
* **Complete Theme System Refactor - Layered Theme Architecture**: Major refactor to replace simple template selection with sophisticated layered theme system (currently stashed)
|
||||
* **Phase 1 - Restore and Assess**:
|
||||
* Restore stashed changes with `git stash pop`
|
||||
* Run tests to identify current failures and validation issues
|
||||
* Assess remaining work by checking all files that still use `--template`
|
||||
* **Phase 2 - Complete CLI Parameter Migration**:
|
||||
* Update remaining CLI commands in asset_commands.py, cli.py, and other files
|
||||
* Fix parameter validation - add proper theme validation for the new string-based parameter
|
||||
* Update help text and documentation to reflect new layered theme capabilities
|
||||
* **Phase 3 - Fix Integration Issues**:
|
||||
* Fix function signature mismatches where functions expect `template` but receive `theme`
|
||||
* Add proper error handling for invalid themes (replace print statements with logging)
|
||||
* Test layered theme functionality - ensure `dark,academic` type combinations work
|
||||
* Verify legacy theme mapping works correctly
|
||||
* **Phase 4 - Quality Assurance**:
|
||||
* Run full test suite to ensure no regressions
|
||||
* Test all CLI commands with new theme parameter
|
||||
* Verify backward compatibility with existing templates
|
||||
* Update any remaining documentation
|
||||
* **Phase 5 - Clean Up and Commit**:
|
||||
* Remove dead code and legacy functions if no longer needed
|
||||
* Ensure consistent terminology throughout codebase
|
||||
* Write comprehensive commit message documenting the major theme system improvement
|
||||
* Update CHANGELOG.md with new theme layering capabilities
|
||||
|
||||
* **To Fix:**
|
||||
* None currently identified
|
||||
|
||||
* **To Refactor:**
|
||||
* None currently identified
|
||||
|
||||
* **To Remove:**
|
||||
* None currently identified
|
||||
|
||||
***
|
||||
|
||||
## Theme System Refactor Context
|
||||
|
||||
**Current State**: Work-in-progress theme system refactor is stashed and partially complete.
|
||||
|
||||
**Completed Parts ✅**:
|
||||
- New Layered Theme Architecture: Complete LAYERED_THEMES system with UI, document, and branding scopes
|
||||
- Theme Parsing Functions: `parse_theme_string()` and `combine_theme_properties()`
|
||||
- CSS Generation Refactor: New `_get_template_css()` and `_generate_layered_css()` methods
|
||||
- CLI Parameter Change: Changed from `--template` to `--theme` throughout test files
|
||||
- Legacy Compatibility: LEGACY_THEME_MAPPING for backward compatibility
|
||||
|
||||
**Missing/Incomplete Parts ❌**:
|
||||
- CLI Parameter Validation: The new `--theme` parameter needs validation for invalid themes
|
||||
- Function Signature Inconsistencies: Some functions still accept `template` parameter but call it with `theme`
|
||||
- Additional Files: Other files in the codebase still use old `template` parameter
|
||||
- Error Handling: The warning system for unknown themes needs proper logging
|
||||
|
||||
**New Capabilities When Complete**:
|
||||
- Single themes: `basic`, `github`, `dark`, `academic`, `light`, `corporate`, `startup`
|
||||
- Layered themes: `dark,academic` combines dark UI with academic typography
|
||||
- Complex combinations: `light,github,corporate` for branded GitHub-style documents
|
||||
- Legacy compatibility: Existing `--template` usage continues to work
|
||||
|
||||
***
|
||||
|
||||
## Completed Tasks
|
||||
|
||||
**CHANGELOG.md Enhancement - COMPLETED ✅**:
|
||||
- ✅ Added missing version entries for 0.1.0, 0.2.0, and 0.3.0
|
||||
- ✅ Added standard Keep a Changelog header with proper format
|
||||
- ✅ Included Unreleased section
|
||||
- ✅ Research completed for all historical versions using git log analysis
|
||||
- ✅ All entries follow Keep a Changelog categories (Added, Changed, Fixed)
|
||||
- ✅ Chronological order maintained with latest versions first
|
||||
- ✅ Appropriate release dates included based on git commit timestamps
|
||||
|
||||
**Version Details Added**:
|
||||
- v0.1.0 (2025-10-15): Development infrastructure, TDD workspace, issue management
|
||||
- v0.2.0 (2025-10-20): Advanced Markdown Engine with GraphQL, search, plugins
|
||||
- v0.3.0 (2025-10-25): Architectural improvements with kaizen-agentic integration
|
||||
@@ -118,6 +118,269 @@ class CleanDocumentManager:
|
||||
|
||||
return version_info
|
||||
|
||||
def _get_template_css(self, template: str = None) -> str:
|
||||
"""Generate layered theme CSS styles."""
|
||||
# Import layered theme functions
|
||||
from markitect.plugins.builtin.markdown_commands import (
|
||||
parse_theme_string, combine_theme_properties, TEMPLATE_STYLES
|
||||
)
|
||||
|
||||
# Handle layered themes or fall back to legacy
|
||||
if template and ',' in template:
|
||||
# New layered theme system
|
||||
theme_list = parse_theme_string(template)
|
||||
combined_props = combine_theme_properties(theme_list)
|
||||
return self._generate_layered_css(combined_props)
|
||||
else:
|
||||
# Legacy single theme or fallback
|
||||
if not template or template not in TEMPLATE_STYLES:
|
||||
# Use default layered themes or the specified theme
|
||||
theme_list = parse_theme_string(template or 'basic')
|
||||
combined_props = combine_theme_properties(theme_list)
|
||||
return self._generate_layered_css(combined_props)
|
||||
else:
|
||||
# Legacy theme - convert to layered
|
||||
theme_list = parse_theme_string(template)
|
||||
combined_props = combine_theme_properties(theme_list)
|
||||
return self._generate_layered_css(combined_props)
|
||||
|
||||
def _generate_layered_css(self, properties: dict) -> str:
|
||||
"""Generate CSS from combined theme properties."""
|
||||
|
||||
# Set defaults for missing properties (properties override defaults)
|
||||
defaults = {
|
||||
'body_background': '#ffffff',
|
||||
'body_color': '#333333',
|
||||
'font_family': '-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif',
|
||||
'max_width': '800px',
|
||||
'heading_color': '#333333', # Use same as body color by default
|
||||
'heading_style': 'simple',
|
||||
'text_align': 'left',
|
||||
'code_background': '#f6f8fa',
|
||||
'code_color': '#333333',
|
||||
'border_color': '#d0d7de',
|
||||
'blockquote_border': '#dfe2e5',
|
||||
'blockquote_color': '#6a737d',
|
||||
'table_border': '#d0d7de',
|
||||
'table_header_bg': '#f6f8fa',
|
||||
'accent_color': None,
|
||||
'secondary_color': None
|
||||
}
|
||||
|
||||
# Merge defaults first, then override with theme properties
|
||||
props = {**defaults, **properties}
|
||||
|
||||
# Base CSS
|
||||
base_css = f"""
|
||||
body {{
|
||||
font-family: {props['font_family']};
|
||||
max-width: {props['max_width']};
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
line-height: 1.6;
|
||||
color: {props['body_color']};
|
||||
background-color: {props['body_background']};
|
||||
}}
|
||||
#markdown-content {{
|
||||
min-height: 200px;
|
||||
}}"""
|
||||
|
||||
# Heading styles
|
||||
heading_css = ""
|
||||
if props['heading_style'] == 'underlined':
|
||||
heading_css = f"""
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {props['heading_color']};
|
||||
border-bottom: 1px solid {props['border_color']};
|
||||
padding-bottom: 0.3em;
|
||||
}}"""
|
||||
elif props['heading_style'] == 'centered':
|
||||
heading_css = f"""
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {props['heading_color']};
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}}
|
||||
h1 {{
|
||||
text-align: center;
|
||||
font-size: 2.2em;
|
||||
border-bottom: 2px solid {props['heading_color']};
|
||||
padding-bottom: 0.5rem;
|
||||
}}"""
|
||||
else: # simple
|
||||
heading_css = f"""
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: {props['heading_color']};
|
||||
}}"""
|
||||
|
||||
# Text alignment
|
||||
text_css = ""
|
||||
if props['text_align'] == 'justify':
|
||||
text_css = """
|
||||
p {
|
||||
text-align: justify;
|
||||
margin-bottom: 1.2rem;
|
||||
}"""
|
||||
|
||||
# Element styling
|
||||
element_css = f"""
|
||||
pre {{
|
||||
background-color: {props['code_background']};
|
||||
color: {props['code_color']};
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
border: 1px solid {props['border_color']};
|
||||
}}
|
||||
code {{
|
||||
background-color: {props['code_background']};
|
||||
color: {props['code_color']};
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}}
|
||||
pre code {{
|
||||
background: none;
|
||||
padding: 0;
|
||||
}}
|
||||
blockquote {{
|
||||
border-left: 4px solid {props['blockquote_border']};
|
||||
margin: 0;
|
||||
padding-left: 1rem;
|
||||
color: {props['blockquote_color']};
|
||||
}}
|
||||
table {{
|
||||
font-size: 0.85em;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
border: 1px solid {props['table_border']};
|
||||
}}
|
||||
th, td {{
|
||||
font-size: inherit;
|
||||
border: 1px solid {props['table_border']};
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}}
|
||||
th {{
|
||||
background-color: {props['table_header_bg']};
|
||||
font-weight: 600;
|
||||
}}"""
|
||||
|
||||
# Link styling
|
||||
link_css = ""
|
||||
if props.get('link_color'):
|
||||
link_css = f"""
|
||||
a {{
|
||||
color: {props['link_color']};
|
||||
text-decoration: underline;
|
||||
}}"""
|
||||
if props.get('link_hover_color'):
|
||||
link_css += f"""
|
||||
a:hover {{
|
||||
color: {props['link_hover_color']};
|
||||
}}"""
|
||||
else:
|
||||
link_css += """
|
||||
a:hover {
|
||||
opacity: 0.8;
|
||||
}"""
|
||||
|
||||
# Branding accents (if specified and no link_color already set)
|
||||
accent_css = ""
|
||||
if props.get('accent_color') and not props.get('link_color'):
|
||||
accent_css = f"""
|
||||
a {{
|
||||
color: {props['accent_color']};
|
||||
}}
|
||||
a:hover {{
|
||||
opacity: 0.8;
|
||||
}}"""
|
||||
|
||||
# UI theme styling for editor interface elements
|
||||
ui_css = ""
|
||||
if props.get('editor_panel_bg'):
|
||||
ui_css = f"""
|
||||
.markitect-edit-mode .ui-edit-floater-panel {{
|
||||
background: {props['editor_panel_bg']};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
box-shadow: 0 4px 12px {props.get('editor_shadow', 'rgba(0,0,0,0.1)')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-floater-header {{
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button {{
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button:hover {{
|
||||
background: {props.get('editor_button_hover', '#e9ecef')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button:active,
|
||||
.markitect-edit-mode .ui-edit-button.active {{
|
||||
background: {props.get('editor_button_active', '#dee2e6')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-accept {{
|
||||
background: {props.get('editor_button_bg', '#4caf50')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-cancel {{
|
||||
background: {props.get('editor_button_bg', '#f44336')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-button-reset {{
|
||||
background: {props.get('editor_button_bg', '#ff9800')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-section-frame {{
|
||||
border: 2px solid {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 3px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea {{
|
||||
border: 1px solid {props.get('editor_panel_border', '#dee2e6')};
|
||||
color: {props.get('editor_text_color', '#212529')};
|
||||
background: {props.get('editor_button_bg', '#ffffff')};
|
||||
}}
|
||||
.markitect-edit-mode .ui-edit-textarea:focus {{
|
||||
border-color: {props.get('editor_focus_color', '#0066cc')};
|
||||
box-shadow: 0 0 0 2px {props.get('editor_focus_color', '#0066cc')}33;
|
||||
}}"""
|
||||
|
||||
return f"<style>{base_css}{heading_css}{text_css}{element_css}{link_css}{accent_css}{ui_css}</style>"
|
||||
|
||||
def _get_legacy_template_css(self, template: str) -> str:
|
||||
"""Legacy CSS generation - kept for backward compatibility."""
|
||||
# Import template styles
|
||||
from markitect.plugins.builtin.markdown_commands import TEMPLATE_STYLES
|
||||
|
||||
# Use basic as default if no template specified
|
||||
if not template or template not in TEMPLATE_STYLES:
|
||||
template = 'basic'
|
||||
|
||||
style_config = TEMPLATE_STYLES[template]
|
||||
|
||||
# Base CSS that's common to all templates
|
||||
base_css = f"""
|
||||
body {{
|
||||
font-family: {style_config['font_family']};
|
||||
max-width: {style_config['max_width']};
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
line-height: 1.6;
|
||||
color: {style_config['body_color']};
|
||||
}}
|
||||
#markdown-content {{
|
||||
min-height: 200px;
|
||||
}}"""
|
||||
|
||||
# Convert legacy template config to layered format
|
||||
legacy_config = TEMPLATE_STYLES[template]
|
||||
layered_props = {
|
||||
'font_family': legacy_config['font_family'],
|
||||
'max_width': legacy_config['max_width'],
|
||||
'body_color': legacy_config['body_color'],
|
||||
}
|
||||
return self._generate_layered_css(layered_props)
|
||||
|
||||
def _generate_html_template(self, markdown_content: str, title: str, css: str = None, template: str = None,
|
||||
edit_mode: bool = False, editor_theme: str = 'github', keyboard_shortcuts: bool = True, original_filename: str = 'document', version_info: dict = None) -> str:
|
||||
"""Generate clean HTML template."""
|
||||
@@ -138,60 +401,8 @@ class CleanDocumentManager:
|
||||
except Exception:
|
||||
css_content = f'<link rel="stylesheet" href="{css}">'
|
||||
|
||||
# Default CSS for basic styling
|
||||
default_css = """
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
#markdown-content {
|
||||
min-height: 200px;
|
||||
}
|
||||
pre {
|
||||
background: #f6f8fa;
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
code {
|
||||
background: #f6f8fa;
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid #dfe2e5;
|
||||
margin: 0;
|
||||
padding-left: 1rem;
|
||||
color: #6a737d;
|
||||
}
|
||||
table {
|
||||
font-size: 0.85em;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
th, td {
|
||||
font-size: inherit;
|
||||
border: 1px solid #dfe2e5;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #f6f8fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
# Generate template-specific CSS
|
||||
default_css = self._get_template_css(template)
|
||||
|
||||
# Load clean editor JavaScript files
|
||||
editor_scripts = ""
|
||||
@@ -823,7 +1034,7 @@ class DOMRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionElement = event.target.closest('.markitect-section-editable');
|
||||
const sectionElement = event.target.closest('.ui-edit-section');
|
||||
if (!sectionElement) return;
|
||||
|
||||
const sectionId = sectionElement.getAttribute('data-section-id');
|
||||
@@ -859,6 +1070,7 @@ class DOMRenderer {
|
||||
`;
|
||||
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.className = 'ui-edit-textarea ui-edit-textarea-main';
|
||||
textarea.value = content;
|
||||
textarea.style.cssText = `
|
||||
flex: 1;
|
||||
@@ -887,6 +1099,16 @@ class DOMRenderer {
|
||||
const createButton = (text, color, handler) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.textContent = text;
|
||||
|
||||
// Add CSS classes based on button type
|
||||
btn.className = 'ui-edit-button';
|
||||
if (text.includes('Accept')) {
|
||||
btn.className += ' ui-edit-button-accept';
|
||||
} else if (text.includes('Cancel')) {
|
||||
btn.className += ' ui-edit-button-cancel';
|
||||
} else if (text.includes('Reset')) {
|
||||
btn.className += ' ui-edit-button-reset';
|
||||
}
|
||||
btn.style.cssText = `
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
@@ -947,7 +1169,7 @@ class DOMRenderer {
|
||||
}
|
||||
|
||||
setupSectionElement(element) {
|
||||
element.className = 'markitect-section-editable';
|
||||
element.className = 'ui-edit-section ui-edit-section-frame';
|
||||
element.style.cssText = `
|
||||
margin: 16px 0;
|
||||
padding: 12px;
|
||||
@@ -1129,16 +1351,17 @@ class MarkitectCleanEditor {
|
||||
addGlobalControls() {
|
||||
// Create floating control panel
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'markitect-global-controls';
|
||||
panel.id = 'ui-edit-floater';
|
||||
panel.className = 'ui-edit-floater-panel';
|
||||
panel.innerHTML = `
|
||||
<div class="control-header">
|
||||
<div class="ui-edit-floater-header">
|
||||
<h3>📝 Editor</h3>
|
||||
<div class="control-status" id="editor-status">Ready</div>
|
||||
<div class="ui-edit-floater-status" id="editor-status">Ready</div>
|
||||
</div>
|
||||
<div class="control-actions">
|
||||
<button id="save-document" class="control-btn primary">💾 Save Document</button>
|
||||
<button id="reset-all" class="control-btn warning">🔄 Reset All</button>
|
||||
<button id="show-status" class="control-btn secondary">📊 Show Status</button>
|
||||
<div class="ui-edit-floater-actions">
|
||||
<button id="save-document" class="ui-edit-button ui-edit-button-accept">💾 Save Document</button>
|
||||
<button id="reset-all" class="ui-edit-button ui-edit-button-reset">🔄 Reset All</button>
|
||||
<button id="show-status" class="ui-edit-button ui-edit-button-secondary">📊 Show Status</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
@@ -26,7 +26,157 @@ def get_default_format(available_formats=['table', 'json', 'yaml', 'simple'], fa
|
||||
return fallback
|
||||
|
||||
|
||||
# Template styles configuration for tests
|
||||
# Layered theme system - themes can be combined across different scopes
|
||||
LAYERED_THEMES = {
|
||||
# Mode Themes - Light/dark color schemes
|
||||
'light': {
|
||||
'scope': 'mode',
|
||||
'properties': {
|
||||
'body_background': '#ffffff',
|
||||
'body_color': '#333333',
|
||||
'heading_color': '#24292f',
|
||||
'code_background': '#f6f8fa',
|
||||
'code_color': '#24292e',
|
||||
'border_color': '#d0d7de',
|
||||
'blockquote_border': '#dfe2e5',
|
||||
'blockquote_color': '#6a737d',
|
||||
'table_border': '#d0d7de',
|
||||
'table_header_bg': '#f6f8fa',
|
||||
'link_color': '#0969da',
|
||||
'link_hover_color': '#0550ae'
|
||||
}
|
||||
},
|
||||
'dark': {
|
||||
'scope': 'mode',
|
||||
'properties': {
|
||||
'body_background': '#0d1117',
|
||||
'body_color': '#e1e4e8',
|
||||
'heading_color': '#58a6ff',
|
||||
'code_background': '#161b22',
|
||||
'code_color': '#e1e4e8',
|
||||
'border_color': '#30363d',
|
||||
'blockquote_border': '#58a6ff',
|
||||
'blockquote_color': '#8b949e',
|
||||
'table_border': '#30363d',
|
||||
'table_header_bg': '#161b22',
|
||||
'link_color': '#79c0ff',
|
||||
'link_hover_color': '#a5d6ff'
|
||||
}
|
||||
},
|
||||
|
||||
# UI Themes - Editor interface elements (floating panels, buttons, editing frames)
|
||||
'standard': {
|
||||
'scope': 'ui',
|
||||
'properties': {
|
||||
'editor_panel_bg': '#f8f9fa',
|
||||
'editor_panel_border': '#dee2e6',
|
||||
'editor_button_bg': '#ffffff',
|
||||
'editor_button_hover': '#e9ecef',
|
||||
'editor_button_active': '#dee2e6',
|
||||
'editor_text_color': '#212529',
|
||||
'editor_focus_color': '#0066cc',
|
||||
'editor_shadow': 'rgba(0,0,0,0.1)'
|
||||
}
|
||||
},
|
||||
'greyscale': {
|
||||
'scope': 'ui',
|
||||
'properties': {
|
||||
'editor_panel_bg': '#f5f5f5',
|
||||
'editor_panel_border': '#d0d0d0',
|
||||
'editor_button_bg': '#ffffff',
|
||||
'editor_button_hover': '#e8e8e8',
|
||||
'editor_button_active': '#d4d4d4',
|
||||
'editor_text_color': '#333333',
|
||||
'editor_focus_color': '#666666',
|
||||
'editor_shadow': 'rgba(0,0,0,0.15)'
|
||||
}
|
||||
},
|
||||
'electric': {
|
||||
'scope': 'ui',
|
||||
'properties': {
|
||||
'editor_panel_bg': '#001122',
|
||||
'editor_panel_border': '#00ffff',
|
||||
'editor_button_bg': '#003366',
|
||||
'editor_button_hover': '#0066cc',
|
||||
'editor_button_active': '#0099ff',
|
||||
'editor_text_color': '#00ffff',
|
||||
'editor_focus_color': '#ffff00',
|
||||
'editor_shadow': 'rgba(0,255,255,0.3)'
|
||||
}
|
||||
},
|
||||
'psychedelic': {
|
||||
'scope': 'ui',
|
||||
'properties': {
|
||||
'editor_panel_bg': 'linear-gradient(45deg, #ff6b35, #f7931e, #ffd23f, #06ffa5)',
|
||||
'editor_panel_border': '#ff1493',
|
||||
'editor_button_bg': 'rgba(255,255,255,0.2)',
|
||||
'editor_button_hover': 'rgba(255,20,147,0.3)',
|
||||
'editor_button_active': 'rgba(255,20,147,0.5)',
|
||||
'editor_text_color': '#ffffff',
|
||||
'editor_focus_color': '#ff1493',
|
||||
'editor_shadow': 'rgba(255,20,147,0.4)'
|
||||
}
|
||||
},
|
||||
|
||||
# Document Themes - Typography and layout
|
||||
'basic': {
|
||||
'scope': 'document',
|
||||
'properties': {
|
||||
'font_family': '-apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif',
|
||||
'max_width': '800px',
|
||||
'heading_style': 'simple',
|
||||
'text_align': 'left'
|
||||
}
|
||||
},
|
||||
'github': {
|
||||
'scope': 'document',
|
||||
'properties': {
|
||||
'font_family': '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif',
|
||||
'max_width': '900px',
|
||||
'heading_style': 'underlined',
|
||||
'text_align': 'left'
|
||||
}
|
||||
},
|
||||
'academic': {
|
||||
'scope': 'document',
|
||||
'properties': {
|
||||
'font_family': 'Georgia, Times New Roman, serif',
|
||||
'max_width': '650px',
|
||||
'heading_style': 'centered',
|
||||
'text_align': 'justify',
|
||||
'link_color': '#777777',
|
||||
'link_hover_color': '#999999'
|
||||
}
|
||||
},
|
||||
|
||||
# Branding Themes - Company/personal styling
|
||||
'corporate': {
|
||||
'scope': 'branding',
|
||||
'properties': {
|
||||
'accent_color': '#0066cc',
|
||||
'secondary_color': '#f8f9fa',
|
||||
'brand_font': 'inherit'
|
||||
}
|
||||
},
|
||||
'startup': {
|
||||
'scope': 'branding',
|
||||
'properties': {
|
||||
'accent_color': '#ff6b35',
|
||||
'secondary_color': '#f4f4f4',
|
||||
'brand_font': 'inherit'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Legacy compatibility - map old theme names to new layered equivalents
|
||||
LEGACY_THEME_MAPPING = {
|
||||
'basic': ['light', 'standard', 'basic'],
|
||||
'github': ['light', 'standard', 'github'],
|
||||
'dark': ['dark', 'standard', 'basic'],
|
||||
'academic': ['light', 'standard', 'academic']
|
||||
}
|
||||
|
||||
# Keep TEMPLATE_STYLES for backward compatibility in tests
|
||||
TEMPLATE_STYLES = {
|
||||
'basic': {
|
||||
'body_color': '#333',
|
||||
@@ -51,21 +201,133 @@ TEMPLATE_STYLES = {
|
||||
}
|
||||
|
||||
|
||||
def generate_html_with_embedded_markdown(markdown_content, title, template, css_content, template_vars):
|
||||
def parse_theme_string(theme_string: str) -> list:
|
||||
"""
|
||||
Parse theme string into list of individual themes.
|
||||
|
||||
Supports:
|
||||
- Single theme: "dark"
|
||||
- Multiple themes: "dark,academic" or "dark, academic"
|
||||
- Legacy theme mapping: "basic" -> ["light", "basic"]
|
||||
|
||||
Args:
|
||||
theme_string: Comma-separated theme names
|
||||
|
||||
Returns:
|
||||
List of theme names in order
|
||||
"""
|
||||
if not theme_string:
|
||||
return ['light', 'basic'] # Default themes
|
||||
|
||||
# Split by comma and clean up whitespace
|
||||
themes = [theme.strip() for theme in theme_string.split(',')]
|
||||
|
||||
# Expand legacy themes only if they don't exist in the new layered system
|
||||
expanded_themes = []
|
||||
for theme in themes:
|
||||
if theme in LAYERED_THEMES:
|
||||
# Theme exists in new system, use as-is
|
||||
expanded_themes.append(theme)
|
||||
elif theme in LEGACY_THEME_MAPPING:
|
||||
# Legacy theme, expand it
|
||||
expanded_themes.extend(LEGACY_THEME_MAPPING[theme])
|
||||
else:
|
||||
# Unknown theme, add as-is (will be warned about later)
|
||||
expanded_themes.append(theme)
|
||||
|
||||
return expanded_themes
|
||||
|
||||
|
||||
class ThemeType(click.ParamType):
|
||||
"""Custom click type for theme validation."""
|
||||
|
||||
name = "theme"
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
try:
|
||||
validate_theme_string(value)
|
||||
return value
|
||||
except click.BadParameter as e:
|
||||
self.fail(str(e), param, ctx)
|
||||
|
||||
|
||||
def validate_theme_string(theme_string: str) -> None:
|
||||
"""
|
||||
Validate that all themes in a theme string are known themes.
|
||||
|
||||
Args:
|
||||
theme_string: Comma-separated theme names
|
||||
|
||||
Raises:
|
||||
click.BadParameter: If any theme is unknown
|
||||
"""
|
||||
if not theme_string:
|
||||
return # Allow empty/None themes
|
||||
|
||||
themes = parse_theme_string(theme_string)
|
||||
unknown_themes = []
|
||||
|
||||
for theme_name in themes:
|
||||
if theme_name not in LAYERED_THEMES and theme_name not in LEGACY_THEME_MAPPING:
|
||||
unknown_themes.append(theme_name)
|
||||
|
||||
if unknown_themes:
|
||||
available_themes = list(LAYERED_THEMES.keys()) + list(LEGACY_THEME_MAPPING.keys())
|
||||
raise click.BadParameter(
|
||||
f"Unknown theme(s): {', '.join(unknown_themes)}. "
|
||||
f"Available themes: {', '.join(sorted(set(available_themes)))}"
|
||||
)
|
||||
|
||||
|
||||
def combine_theme_properties(theme_list: list) -> dict:
|
||||
"""
|
||||
Combine properties from multiple themes, with later themes overriding earlier ones.
|
||||
|
||||
Args:
|
||||
theme_list: List of theme names in order of application
|
||||
|
||||
Returns:
|
||||
Combined properties dictionary
|
||||
"""
|
||||
combined_properties = {}
|
||||
|
||||
for theme_name in theme_list:
|
||||
if theme_name in LAYERED_THEMES:
|
||||
theme_data = LAYERED_THEMES[theme_name]
|
||||
# Later themes override earlier ones
|
||||
combined_properties.update(theme_data['properties'])
|
||||
elif theme_name in LEGACY_THEME_MAPPING:
|
||||
# Handle legacy themes by expanding them
|
||||
expanded_themes = LEGACY_THEME_MAPPING[theme_name]
|
||||
for expanded_theme in expanded_themes:
|
||||
if expanded_theme in LAYERED_THEMES:
|
||||
theme_data = LAYERED_THEMES[expanded_theme]
|
||||
combined_properties.update(theme_data['properties'])
|
||||
else:
|
||||
# This should not happen if validation is working
|
||||
print(f"Warning: Unknown theme '{theme_name}' - skipping")
|
||||
return combined_properties
|
||||
|
||||
|
||||
def generate_html_with_embedded_markdown(markdown_content, title, theme, css_content, template_vars):
|
||||
"""
|
||||
Generate HTML with embedded markdown content for testing.
|
||||
|
||||
This function is used by tests to validate template functionality.
|
||||
"""
|
||||
# Create a temporary document manager for rendering
|
||||
doc_manager = DocumentManager(None)
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(None)
|
||||
|
||||
# Generate HTML template
|
||||
html_content = doc_manager._generate_html_template(
|
||||
markdown_content=markdown_content,
|
||||
title=title,
|
||||
css=css_content,
|
||||
template=template
|
||||
template=theme
|
||||
)
|
||||
|
||||
return html_content
|
||||
@@ -191,7 +453,8 @@ def process_single_file(input_file: Path, use_publication_dir: bool, publication
|
||||
output_file = input_file.with_suffix('.html')
|
||||
|
||||
# Create document manager and render
|
||||
doc_manager = DocumentManager(None)
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(None)
|
||||
doc_manager.render_file(str(input_file), str(output_file))
|
||||
|
||||
return output_file
|
||||
@@ -212,7 +475,8 @@ def process_directory(input_dir: Path, use_publication_dir: bool, publication_di
|
||||
markdown_files = find_markdown_files(input_dir)
|
||||
output_files = []
|
||||
|
||||
doc_manager = DocumentManager(None)
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(None)
|
||||
|
||||
for md_file in markdown_files:
|
||||
if use_publication_dir:
|
||||
@@ -304,21 +568,22 @@ def extract_html_title(html_file: Path) -> str:
|
||||
return html_file.stem
|
||||
|
||||
|
||||
def generate_index_html(html_files: list, title: str, template: str = None) -> str:
|
||||
def generate_index_html(html_files: list, title: str, theme: str = None) -> str:
|
||||
"""
|
||||
Generate HTML content for an index page.
|
||||
|
||||
Args:
|
||||
html_files: List of dictionaries with 'path', 'title', and 'relative_path' keys
|
||||
title: Title for the index page
|
||||
template: Template theme to use
|
||||
theme: Theme to use
|
||||
|
||||
Returns:
|
||||
HTML content string
|
||||
"""
|
||||
# Get template CSS
|
||||
doc_manager = DocumentManager(None)
|
||||
template_css = doc_manager._get_template_css(template)
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(None)
|
||||
template_css = doc_manager._get_template_css(theme)
|
||||
|
||||
# Generate file list HTML
|
||||
if not html_files:
|
||||
@@ -1497,6 +1762,7 @@ class MarkdownCommandsPlugin(CommandPlugin):
|
||||
'md-get': md_get_command,
|
||||
'md-list': md_list_command,
|
||||
'md-render': md_render_command,
|
||||
'themes': themes_list_command,
|
||||
'md-index': md_index_command,
|
||||
'md-explode': md_explode_command,
|
||||
'md-implode': md_implode_command,
|
||||
@@ -1530,7 +1796,8 @@ def md_ingest_command(ctx, file_path):
|
||||
click.echo(f"Processing file: {file_path}")
|
||||
|
||||
# Initialize document manager with database manager
|
||||
doc_manager = DocumentManager(config.get('db_manager'))
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
|
||||
# Process the file
|
||||
result = doc_manager.ingest_file(Path(file_path))
|
||||
@@ -1571,7 +1838,8 @@ def md_get_command(ctx, file_path, output):
|
||||
config = ctx.obj or {}
|
||||
try:
|
||||
# Initialize document manager
|
||||
doc_manager = DocumentManager(config.get('db_manager'))
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
|
||||
# Get file information
|
||||
result = doc_manager.get_file(file_path)
|
||||
@@ -1620,7 +1888,8 @@ def md_list_command(ctx, output_format, names_only):
|
||||
config = ctx.obj or {}
|
||||
try:
|
||||
# Initialize document manager
|
||||
doc_manager = DocumentManager(config.get('db_manager'))
|
||||
from markitect.clean_document_manager import CleanDocumentManager
|
||||
doc_manager = CleanDocumentManager(config.get('db_manager'))
|
||||
|
||||
# Get file listing
|
||||
files = doc_manager.list_files()
|
||||
@@ -1654,8 +1923,8 @@ def md_list_command(ctx, output_format, names_only):
|
||||
@click.argument('input_file', type=click.Path(exists=True))
|
||||
@click.option('--output', '-o', type=click.Path(),
|
||||
help='Output HTML file (default: <input>.html)')
|
||||
@click.option('--template', type=click.Choice(['basic', 'github', 'dark', 'academic']),
|
||||
help='Built-in template theme (basic, github, dark, academic)')
|
||||
@click.option('--theme', type=ThemeType(),
|
||||
help='Theme(s) to apply. Single: dark or layered: dark,academic or light,github,corporate. Available: basic, github, dark, academic, light, corporate, startup')
|
||||
@click.option('--css', type=click.Path(),
|
||||
help='Custom CSS file to include')
|
||||
@click.option('--edit', is_flag=True,
|
||||
@@ -1670,22 +1939,28 @@ def md_list_command(ctx, output_format, names_only):
|
||||
@click.option('--dont-use-publication-dir', is_flag=True,
|
||||
help='Don\'t use publication directory for output')
|
||||
@click.pass_context
|
||||
def md_render_command(ctx, input_file, output, template, css, edit, editor_theme,
|
||||
def md_render_command(ctx, input_file, output, theme, css, edit, editor_theme,
|
||||
keyboard_shortcuts, use_publication_dir, dont_use_publication_dir):
|
||||
"""
|
||||
Render a markdown file to HTML with basic templates and live preview capabilities.
|
||||
|
||||
Converts a markdown file to HTML using customizable templates and styles.
|
||||
Converts a markdown file to HTML using customizable layered themes and styles.
|
||||
Supports live editing mode with real-time preview and syntax highlighting.
|
||||
Choose from basic, github, dark, or academic themes for professional output.
|
||||
|
||||
Theme Layering:
|
||||
- Single themes: basic, github, dark, academic, light, corporate, startup
|
||||
- Layered themes: dark,academic combines dark UI with academic typography
|
||||
- Later themes override settings from earlier themes
|
||||
|
||||
INPUT_FILE: Path to the markdown file to render
|
||||
|
||||
Examples:
|
||||
markitect md-render README.md
|
||||
markitect md-render docs/guide.md --output guide.html --template github
|
||||
markitect md-render docs/guide.md --output guide.html --theme github
|
||||
markitect md-render draft.md --edit --editor-theme monokai
|
||||
markitect md-render doc.md --template dark --css custom.css
|
||||
markitect md-render doc.md --theme dark --css custom.css
|
||||
markitect md-render doc.md --theme dark,academic
|
||||
markitect md-render doc.md --theme light,github,corporate
|
||||
"""
|
||||
config = ctx.obj or {}
|
||||
|
||||
@@ -1712,7 +1987,7 @@ def md_render_command(ctx, input_file, output, template, css, edit, editor_theme
|
||||
if edit:
|
||||
# Edit mode - generate HTML with editing capabilities
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=template, css=css,
|
||||
template=theme, css=css,
|
||||
edit_mode=True,
|
||||
editor_theme=editor_theme,
|
||||
keyboard_shortcuts=keyboard_shortcuts)
|
||||
@@ -1722,16 +1997,16 @@ def md_render_command(ctx, input_file, output, template, css, edit, editor_theme
|
||||
if config.get('verbose', False):
|
||||
click.echo(f"Editor theme: {editor_theme}")
|
||||
click.echo(f"Keyboard shortcuts: {'enabled' if keyboard_shortcuts else 'disabled'}")
|
||||
click.echo(f"Template: {template or 'default'}")
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
else:
|
||||
# Static render
|
||||
result = doc_manager.render_file(input_file, str(output_path),
|
||||
template=template, css=css)
|
||||
template=theme, css=css)
|
||||
click.echo(f"✓ Rendered to: {output_path}")
|
||||
|
||||
if config.get('verbose', False):
|
||||
click.echo(f"Template: {template or 'default'}")
|
||||
click.echo(f"Theme: {theme or 'default'}")
|
||||
click.echo(f"CSS: {css or 'default'}")
|
||||
|
||||
except Exception as e:
|
||||
@@ -1739,16 +2014,126 @@ def md_render_command(ctx, input_file, output, template, css, edit, editor_theme
|
||||
raise click.Abort()
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option('--format', type=click.Choice(['table', 'list', 'json']), default='table',
|
||||
help='Output format: table (default), list, or json')
|
||||
@click.option('--scope', type=click.Choice(['mode', 'ui', 'document', 'branding', 'all']), default='all',
|
||||
help='Filter themes by scope: mode (light/dark), ui (editor interface), document (typography), branding (colors), or all (default)')
|
||||
def themes_list_command(format, scope):
|
||||
"""
|
||||
List all available themes and their properties.
|
||||
|
||||
Shows the available themes that can be used with md-render and other commands.
|
||||
Themes can be used individually or combined in layers.
|
||||
|
||||
Examples:
|
||||
markitect themes list
|
||||
markitect themes list --format json
|
||||
markitect themes list --scope ui
|
||||
markitect themes list --scope document --format list
|
||||
"""
|
||||
from tabulate import tabulate
|
||||
import json
|
||||
|
||||
# Get theme data
|
||||
layered_themes = []
|
||||
legacy_mappings = []
|
||||
|
||||
# Process layered themes
|
||||
for theme_name, theme_data in LAYERED_THEMES.items():
|
||||
theme_scope = theme_data['scope']
|
||||
if scope == 'all' or scope == theme_scope:
|
||||
properties = theme_data['properties']
|
||||
# Get key properties for display based on scope
|
||||
key_props = []
|
||||
if theme_scope == 'mode':
|
||||
if 'body_background' in properties:
|
||||
key_props.append(f"bg:{properties['body_background']}")
|
||||
if 'link_color' in properties:
|
||||
key_props.append(f"links:{properties['link_color']}")
|
||||
elif theme_scope == 'ui':
|
||||
if 'editor_panel_bg' in properties:
|
||||
key_props.append(f"panel:{properties['editor_panel_bg']}")
|
||||
if 'editor_text_color' in properties:
|
||||
key_props.append(f"text:{properties['editor_text_color']}")
|
||||
if 'editor_focus_color' in properties:
|
||||
key_props.append(f"focus:{properties['editor_focus_color']}")
|
||||
elif theme_scope == 'document':
|
||||
if 'font_family' in properties:
|
||||
family = properties['font_family'].split(',')[0].strip().strip('"\'')
|
||||
key_props.append(f"font:{family}")
|
||||
if 'link_color' in properties:
|
||||
key_props.append(f"links:{properties['link_color']}")
|
||||
elif theme_scope == 'branding':
|
||||
if 'accent_color' in properties:
|
||||
key_props.append(f"accent:{properties['accent_color']}")
|
||||
|
||||
layered_themes.append({
|
||||
'name': theme_name,
|
||||
'scope': theme_scope,
|
||||
'properties': ', '.join(key_props) if key_props else 'default styling'
|
||||
})
|
||||
|
||||
# Process legacy mappings
|
||||
for legacy_name, expanded_themes in LEGACY_THEME_MAPPING.items():
|
||||
legacy_mappings.append({
|
||||
'name': legacy_name,
|
||||
'expands_to': ' + '.join(expanded_themes)
|
||||
})
|
||||
|
||||
if format == 'json':
|
||||
# JSON output
|
||||
output_data = {
|
||||
'layered_themes': layered_themes,
|
||||
'legacy_mappings': legacy_mappings,
|
||||
'usage': {
|
||||
'single': 'markitect md-render file.md --theme dark',
|
||||
'layered': 'markitect md-render file.md --theme dark,academic',
|
||||
'legacy': 'markitect md-render file.md --theme github'
|
||||
}
|
||||
}
|
||||
click.echo(json.dumps(output_data, indent=2))
|
||||
|
||||
elif format == 'list':
|
||||
# Simple list output
|
||||
click.echo("Available themes:")
|
||||
for theme in layered_themes:
|
||||
click.echo(f" {theme['name']} ({theme['scope']})")
|
||||
if legacy_mappings:
|
||||
click.echo("\nLegacy mappings:")
|
||||
for mapping in legacy_mappings:
|
||||
click.echo(f" {mapping['name']} -> {mapping['expands_to']}")
|
||||
|
||||
else: # table format (default)
|
||||
# Table output
|
||||
if layered_themes:
|
||||
click.echo("Layered themes (can be combined):")
|
||||
headers = ['Theme', 'Scope', 'Key Properties']
|
||||
table_data = [[t['name'], t['scope'], t['properties']] for t in layered_themes]
|
||||
click.echo(tabulate(table_data, headers=headers, tablefmt='grid'))
|
||||
|
||||
if legacy_mappings:
|
||||
click.echo("\nLegacy theme mappings:")
|
||||
headers = ['Legacy Name', 'Expands To']
|
||||
table_data = [[m['name'], m['expands_to']] for m in legacy_mappings]
|
||||
click.echo(tabulate(table_data, headers=headers, tablefmt='grid'))
|
||||
|
||||
click.echo("\nUsage examples:")
|
||||
click.echo(" Single theme: markitect md-render file.md --theme dark")
|
||||
click.echo(" Layered themes: markitect md-render file.md --theme dark,academic")
|
||||
click.echo(" Legacy mapping: markitect md-render file.md --theme github")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('directory', type=click.Path(exists=True, file_okay=False, dir_okay=True))
|
||||
@click.option('--output', '-o', type=click.Path(),
|
||||
help='Output index file (default: <directory>/index.html)')
|
||||
@click.option('--template', type=click.Choice(['basic', 'github', 'dark', 'academic']),
|
||||
help='Built-in template theme for index')
|
||||
@click.option('--theme', type=ThemeType(),
|
||||
help='Theme(s) to apply to index. Single: dark or layered: dark,github. Available: basic, github, dark, academic, light, corporate, startup')
|
||||
@click.option('--recursive', '-r', is_flag=True,
|
||||
help='Include subdirectories recursively')
|
||||
@click.pass_context
|
||||
def md_index_command(ctx, directory, output, template, recursive):
|
||||
def md_index_command(ctx, directory, output, theme, recursive):
|
||||
"""
|
||||
Generate an index page for HTML files in a directory.
|
||||
|
||||
@@ -1800,7 +2185,7 @@ def md_index_command(ctx, directory, output, template, recursive):
|
||||
index_title = f"Index - {dir_path.name}"
|
||||
|
||||
# Generate HTML content
|
||||
html_content = generate_index_html(file_info_list, index_title, template)
|
||||
html_content = generate_index_html(file_info_list, index_title, theme)
|
||||
|
||||
# Write index file
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -93,7 +93,7 @@ markitect md-render input.md --output result.html
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', 'github'
|
||||
'--theme', 'github'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
@@ -139,7 +139,7 @@ markitect md-render input.md --output result.html
|
||||
assert 'markdown' in result.output.lower()
|
||||
assert 'html' in result.output.lower()
|
||||
assert '--output' in result.output
|
||||
assert '--template' in result.output
|
||||
assert '--theme' in result.output
|
||||
assert 'basic' in result.output
|
||||
assert 'github' in result.output
|
||||
assert 'dark' in result.output
|
||||
@@ -176,12 +176,14 @@ markitect md-render input.md --output result.html
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', 'invalid_template_name'
|
||||
'--theme', 'invalid_template_name'
|
||||
])
|
||||
|
||||
# Should exit with error code (Click choice validation)
|
||||
assert result.exit_code != 0
|
||||
assert 'invalid choice' in result.output.lower() or 'not one of' in result.output.lower()
|
||||
assert ('invalid choice' in result.output.lower() or
|
||||
'not one of' in result.output.lower() or
|
||||
'unknown theme' in result.output.lower())
|
||||
|
||||
def test_output_directory_creation(self):
|
||||
"""Test that output directory is created if it doesn't exist - Issue #132."""
|
||||
|
||||
@@ -85,7 +85,7 @@ This is a test document for template system validation.
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', 'github'
|
||||
'--theme', 'github'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
@@ -262,7 +262,7 @@ This is a test document for template system validation.
|
||||
def test_multiple_templates_available(self):
|
||||
"""Test that multiple template options are available - Issue #132."""
|
||||
# Test template availability
|
||||
template_options = ['basic', 'github', 'academic', 'dark']
|
||||
theme_options = ['basic', 'github', 'academic', 'dark']
|
||||
|
||||
from markitect.plugins.builtin.markdown_commands import md_render_command
|
||||
from click.testing import CliRunner
|
||||
@@ -273,13 +273,13 @@ This is a test document for template system validation.
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
for template in template_options:
|
||||
output_file = Path(self.temp_dir) / f"{template}_output.html"
|
||||
for theme in theme_options:
|
||||
output_file = Path(self.temp_dir) / f"{theme}_output.html"
|
||||
|
||||
result = runner.invoke(md_render_command, [
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', template
|
||||
'--theme', theme
|
||||
])
|
||||
|
||||
# Should be able to specify different templates
|
||||
@@ -304,7 +304,7 @@ This is a test document for template system validation.
|
||||
result = runner.invoke(md_render_command, [
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', 'dark'
|
||||
'--theme', 'dark'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
@@ -332,12 +332,14 @@ This is a test document for template system validation.
|
||||
result = runner.invoke(cli, [
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--template', 'nonexistent_template'
|
||||
'--theme', 'nonexistent_template'
|
||||
])
|
||||
|
||||
# Should exit with error code for invalid template choice
|
||||
assert result.exit_code != 0
|
||||
assert 'invalid choice' in result.output.lower() or 'not one of' in result.output.lower()
|
||||
assert ('invalid choice' in result.output.lower() or
|
||||
'not one of' in result.output.lower() or
|
||||
'unknown theme' in result.output.lower())
|
||||
|
||||
def test_template_title_extraction_from_markdown(self):
|
||||
"""Test title extraction from markdown for template variables - Issue #132."""
|
||||
|
||||
@@ -94,7 +94,7 @@ Content paragraph that should be editable.
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', template,
|
||||
'--theme', template,
|
||||
'--edit'
|
||||
])
|
||||
|
||||
@@ -145,7 +145,7 @@ Content paragraph that should be editable.
|
||||
'md-render',
|
||||
str(input_file),
|
||||
'--output', str(output_file),
|
||||
'--template', 'github'
|
||||
'--theme', 'github'
|
||||
])
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
||||
@@ -410,10 +410,10 @@ class TestCLIIntegration:
|
||||
assert result.returncode == 0
|
||||
assert custom_output.exists()
|
||||
|
||||
def test_md_index_command_with_template_option(self):
|
||||
"""Test md-index command with template option."""
|
||||
def test_md_index_command_with_theme_option(self):
|
||||
"""Test md-index command with theme option."""
|
||||
result = subprocess.run(
|
||||
["markitect", "md-index", str(self.test_dir), "--template", "github"],
|
||||
["markitect", "md-index", str(self.test_dir), "--theme", "github"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30
|
||||
|
||||
106
tools/register-agents-claude.py
Executable file
106
tools/register-agents-claude.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Bridge script to register kaizen-agentic agents with Claude Code using lazy loading."""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add the kaizen-agentic source to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "capabilities" / "kaizen-agentic" / "src"))
|
||||
|
||||
from kaizen_agentic.registry import AgentRegistry
|
||||
|
||||
|
||||
def get_agent_file_path(agent: object, agents_dir: Path) -> str:
|
||||
"""Get relative path to agent file for lazy loading."""
|
||||
return str(agent.file_path.relative_to(agents_dir.parent))
|
||||
|
||||
|
||||
def generate_claude_agent_configs(agents_dir: Path) -> dict:
|
||||
"""Generate Claude Code agent configurations with metadata only for lazy loading."""
|
||||
registry = AgentRegistry(agents_dir)
|
||||
agents = registry.list_agents()
|
||||
|
||||
claude_agents = {}
|
||||
|
||||
for agent in agents:
|
||||
# Map agent names for Claude Code compatibility
|
||||
claude_name = agent.name.replace('-', '_')
|
||||
if claude_name == "changelog_keeper":
|
||||
claude_name = "keepaChangelog"
|
||||
elif claude_name == "todo_keeper":
|
||||
claude_name = "keepaTodofile"
|
||||
elif claude_name == "contributing_keeper":
|
||||
claude_name = "keepaContributingfile"
|
||||
|
||||
# Store only metadata - instructions will be loaded lazily when needed
|
||||
claude_agents[claude_name] = {
|
||||
"description": agent.description,
|
||||
"category": agent.category.value,
|
||||
"dependencies": list(agent.dependencies),
|
||||
"tools": ["Read", "Write", "Edit", "Glob", "Grep"], # Standard tools
|
||||
"original_name": agent.name,
|
||||
"file_path": get_agent_file_path(agent, agents_dir)
|
||||
}
|
||||
|
||||
return claude_agents
|
||||
|
||||
|
||||
def update_claude_settings(claude_agents: dict, settings_file: Path):
|
||||
"""Update Claude Code settings with agent configurations."""
|
||||
# Load existing settings
|
||||
if settings_file.exists():
|
||||
with open(settings_file, 'r') as f:
|
||||
settings = json.load(f)
|
||||
else:
|
||||
settings = {}
|
||||
|
||||
# Add agents section
|
||||
if "agents" not in settings:
|
||||
settings["agents"] = {}
|
||||
|
||||
# Update with new agent configurations
|
||||
settings["agents"].update(claude_agents)
|
||||
|
||||
# Write updated settings
|
||||
with open(settings_file, 'w') as f:
|
||||
json.dump(settings, f, indent=2)
|
||||
|
||||
return len(claude_agents)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main registration process."""
|
||||
project_root = Path(__file__).parent.parent
|
||||
agents_dir = project_root / "agents"
|
||||
claude_settings = project_root / ".claude" / "settings.local.json"
|
||||
|
||||
if not agents_dir.exists():
|
||||
print(f"Error: Agents directory not found: {agents_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loading agents from kaizen-agentic registry...")
|
||||
try:
|
||||
claude_agents = generate_claude_agent_configs(agents_dir)
|
||||
print(f"Found {len(claude_agents)} agents to register")
|
||||
|
||||
# Show what will be registered
|
||||
for name, config in claude_agents.items():
|
||||
print(f" • {name}: {config['description'][:60]}... (from {config['file_path']})")
|
||||
|
||||
print(f"\nUpdating Claude Code settings: {claude_settings}")
|
||||
count = update_claude_settings(claude_agents, claude_settings)
|
||||
|
||||
print(f"✅ Successfully registered {count} agents with Claude Code (metadata only)")
|
||||
print("📄 Agent instructions will be loaded lazily when needed")
|
||||
print("\nAvailable agents for Task tool:")
|
||||
for name in sorted(claude_agents.keys()):
|
||||
print(f" - {name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user