deckbuilder 1.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deckbuilder/__init__.py +22 -0
- deckbuilder/cli.py +544 -0
- deckbuilder/cli_tools.py +739 -0
- deckbuilder/engine.py +1546 -0
- deckbuilder/image_handler.py +291 -0
- deckbuilder/layout_intelligence.json +288 -0
- deckbuilder/layout_intelligence.py +398 -0
- deckbuilder/naming_conventions.py +541 -0
- deckbuilder/placeholder_types.py +101 -0
- deckbuilder/placekitten_integration.py +280 -0
- deckbuilder/structured_frontmatter.py +862 -0
- deckbuilder/table_styles.py +37 -0
- deckbuilder-1.0.0b1.dist-info/METADATA +378 -0
- deckbuilder-1.0.0b1.dist-info/RECORD +37 -0
- deckbuilder-1.0.0b1.dist-info/WHEEL +5 -0
- deckbuilder-1.0.0b1.dist-info/entry_points.txt +3 -0
- deckbuilder-1.0.0b1.dist-info/licenses/LICENSE +201 -0
- deckbuilder-1.0.0b1.dist-info/top_level.txt +4 -0
- mcp_server/__init__.py +9 -0
- mcp_server/content_analysis.py +436 -0
- mcp_server/content_optimization.py +822 -0
- mcp_server/layout_recommendations.py +595 -0
- mcp_server/main.py +550 -0
- mcp_server/tools.py +492 -0
- placekitten/README.md +561 -0
- placekitten/__init__.py +44 -0
- placekitten/core.py +184 -0
- placekitten/filters.py +183 -0
- placekitten/images/ACuteKitten-1.png +0 -0
- placekitten/images/ACuteKitten-2.png +0 -0
- placekitten/images/ACuteKitten-3.png +0 -0
- placekitten/images/TwoKitttens Playing-1.png +0 -0
- placekitten/images/TwoKitttens Playing-2.png +0 -0
- placekitten/images/TwoKitttensSleeping-1.png +0 -0
- placekitten/processor.py +262 -0
- placekitten/smart_crop.py +314 -0
- shared/__init__.py +9 -0
deckbuilder/__init__.py
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
"""
|
2
|
+
Deckbuilder - PowerPoint Presentation Engine
|
3
|
+
|
4
|
+
Core presentation generation engine with template support and
|
5
|
+
structured frontmatter processing.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from .engine import Deckbuilder, get_deckbuilder_client
|
9
|
+
from .structured_frontmatter import (
|
10
|
+
StructuredFrontmatterConverter,
|
11
|
+
StructuredFrontmatterRegistry,
|
12
|
+
StructuredFrontmatterValidator,
|
13
|
+
)
|
14
|
+
|
15
|
+
__version__ = "1.0.0"
|
16
|
+
__all__ = [
|
17
|
+
"Deckbuilder",
|
18
|
+
"get_deckbuilder_client",
|
19
|
+
"StructuredFrontmatterRegistry",
|
20
|
+
"StructuredFrontmatterConverter",
|
21
|
+
"StructuredFrontmatterValidator",
|
22
|
+
]
|
deckbuilder/cli.py
ADDED
@@ -0,0 +1,544 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Deckbuilder CLI - Standalone Command Line Interface
|
4
|
+
|
5
|
+
Complete command-line interface for Deckbuilder presentation generation,
|
6
|
+
template management, and PlaceKitten image processing. Designed for
|
7
|
+
local development and standalone usage without MCP server dependency.
|
8
|
+
|
9
|
+
Usage:
|
10
|
+
deckbuilder create presentation.md
|
11
|
+
deckbuilder analyze default
|
12
|
+
deckbuilder generate-image 800 600 --filter grayscale
|
13
|
+
"""
|
14
|
+
|
15
|
+
import argparse
|
16
|
+
import json
|
17
|
+
import os
|
18
|
+
import sys
|
19
|
+
from pathlib import Path
|
20
|
+
from typing import Optional
|
21
|
+
|
22
|
+
# Conditional imports for development vs installed package
|
23
|
+
try:
|
24
|
+
# Try package imports first (for installed package)
|
25
|
+
from deckbuilder.engine import Deckbuilder
|
26
|
+
from deckbuilder.cli_tools import TemplateManager
|
27
|
+
from placekitten import PlaceKitten
|
28
|
+
except ImportError:
|
29
|
+
# Fallback to development imports (when running from source)
|
30
|
+
current_dir = Path(__file__).parent
|
31
|
+
project_root = current_dir.parent.parent
|
32
|
+
sys.path.insert(0, str(project_root))
|
33
|
+
|
34
|
+
from src.deckbuilder.engine import Deckbuilder # noqa: E402
|
35
|
+
from src.deckbuilder.cli_tools import TemplateManager # noqa: E402
|
36
|
+
from src.placekitten import PlaceKitten # noqa: E402
|
37
|
+
|
38
|
+
|
39
|
+
class DeckbuilderCLI:
|
40
|
+
"""Standalone Deckbuilder command-line interface"""
|
41
|
+
|
42
|
+
def __init__(self, templates_path=None, output_path=None):
|
43
|
+
self.setup_environment(templates_path, output_path)
|
44
|
+
|
45
|
+
def setup_environment(self, templates_path=None, output_path=None):
|
46
|
+
"""Setup environment variables with priority: CLI args > env vars > defaults"""
|
47
|
+
|
48
|
+
# Template folder resolution priority
|
49
|
+
if templates_path:
|
50
|
+
# 1. CLI argument has highest priority
|
51
|
+
os.environ["DECK_TEMPLATE_FOLDER"] = str(Path(templates_path).resolve())
|
52
|
+
elif not os.getenv("DECK_TEMPLATE_FOLDER"):
|
53
|
+
# 2. Environment variable already set (skip)
|
54
|
+
# 3. Default location: ./templates/
|
55
|
+
default_templates = Path.cwd() / "templates"
|
56
|
+
if default_templates.exists():
|
57
|
+
os.environ["DECK_TEMPLATE_FOLDER"] = str(default_templates)
|
58
|
+
else:
|
59
|
+
# Will trigger error message in commands that need templates
|
60
|
+
pass
|
61
|
+
|
62
|
+
# Output folder resolution priority
|
63
|
+
if output_path:
|
64
|
+
# 1. CLI argument has highest priority
|
65
|
+
output_folder = Path(output_path)
|
66
|
+
output_folder.mkdir(parents=True, exist_ok=True)
|
67
|
+
os.environ["DECK_OUTPUT_FOLDER"] = str(output_folder.resolve())
|
68
|
+
elif not os.getenv("DECK_OUTPUT_FOLDER"):
|
69
|
+
# 2. Environment variable already set (skip)
|
70
|
+
# 3. Default location: current directory
|
71
|
+
os.environ["DECK_OUTPUT_FOLDER"] = str(Path.cwd())
|
72
|
+
|
73
|
+
# Default template name
|
74
|
+
if not os.getenv("DECK_TEMPLATE_NAME"):
|
75
|
+
os.environ["DECK_TEMPLATE_NAME"] = "default"
|
76
|
+
|
77
|
+
def _validate_templates_folder(self):
|
78
|
+
"""Validate templates folder exists and provide helpful error message"""
|
79
|
+
template_folder = os.getenv("DECK_TEMPLATE_FOLDER")
|
80
|
+
if not template_folder or not Path(template_folder).exists():
|
81
|
+
print("❌ Template folder not found: ./templates/")
|
82
|
+
print("💡 Run 'deckbuilder init' to create template folder and copy default files")
|
83
|
+
return False
|
84
|
+
return True
|
85
|
+
|
86
|
+
def _get_available_templates(self):
|
87
|
+
"""Get list of available templates with error handling"""
|
88
|
+
template_folder = os.getenv("DECK_TEMPLATE_FOLDER")
|
89
|
+
if not template_folder or not Path(template_folder).exists():
|
90
|
+
return []
|
91
|
+
|
92
|
+
template_path = Path(template_folder)
|
93
|
+
return [template.stem for template in template_path.glob("*.pptx")]
|
94
|
+
|
95
|
+
def create_presentation(
|
96
|
+
self, input_file: str, output_name: Optional[str] = None, template: Optional[str] = None
|
97
|
+
) -> str:
|
98
|
+
"""
|
99
|
+
Create presentation from markdown or JSON file
|
100
|
+
|
101
|
+
Args:
|
102
|
+
input_file: Path to markdown (.md) or JSON (.json) input file
|
103
|
+
output_name: Optional output filename (without extension)
|
104
|
+
template: Optional template name to use
|
105
|
+
|
106
|
+
Returns:
|
107
|
+
str: Path to generated presentation file
|
108
|
+
"""
|
109
|
+
input_path = Path(input_file)
|
110
|
+
|
111
|
+
if not input_path.exists():
|
112
|
+
print(f"❌ Input file not found: {input_file}")
|
113
|
+
print("💡 Check file path or create the file first")
|
114
|
+
raise FileNotFoundError(f"Input file not found: {input_file}")
|
115
|
+
|
116
|
+
# Validate templates folder exists
|
117
|
+
if not self._validate_templates_folder():
|
118
|
+
return
|
119
|
+
|
120
|
+
# Determine output filename
|
121
|
+
if not output_name:
|
122
|
+
output_name = input_path.stem
|
123
|
+
|
124
|
+
# Set template if provided
|
125
|
+
if template:
|
126
|
+
os.environ["DECK_TEMPLATE_NAME"] = template
|
127
|
+
|
128
|
+
# Initialize Deckbuilder
|
129
|
+
db = Deckbuilder()
|
130
|
+
|
131
|
+
try:
|
132
|
+
if input_path.suffix.lower() == ".md":
|
133
|
+
# Process markdown file
|
134
|
+
content = input_path.read_text(encoding="utf-8")
|
135
|
+
result = db.create_presentation_from_markdown(
|
136
|
+
markdown_content=content, fileName=output_name
|
137
|
+
)
|
138
|
+
elif input_path.suffix.lower() == ".json":
|
139
|
+
# Process JSON file
|
140
|
+
with open(input_path, "r", encoding="utf-8") as f:
|
141
|
+
json_data = json.load(f)
|
142
|
+
result = db.create_presentation(json_data=json_data, fileName=output_name)
|
143
|
+
else:
|
144
|
+
raise ValueError(
|
145
|
+
f"Unsupported file format: {input_path.suffix}. "
|
146
|
+
"Supported formats: .md, .json"
|
147
|
+
)
|
148
|
+
|
149
|
+
print(f"✅ Presentation created successfully: {result}")
|
150
|
+
return result
|
151
|
+
|
152
|
+
except Exception as e:
|
153
|
+
print(f"❌ Error creating presentation: {e}")
|
154
|
+
raise
|
155
|
+
|
156
|
+
def analyze_template(self, template_name: str = "default", verbose: bool = False):
|
157
|
+
"""Analyze PowerPoint template structure"""
|
158
|
+
if not self._validate_templates_folder():
|
159
|
+
return
|
160
|
+
manager = TemplateManager()
|
161
|
+
manager.analyze_template(template_name, verbose=verbose)
|
162
|
+
|
163
|
+
def validate_template(self, template_name: str = "default"):
|
164
|
+
"""Validate template and mappings"""
|
165
|
+
if not self._validate_templates_folder():
|
166
|
+
return
|
167
|
+
manager = TemplateManager()
|
168
|
+
manager.validate_template(template_name)
|
169
|
+
|
170
|
+
def document_template(self, template_name: str = "default", output_file: Optional[str] = None):
|
171
|
+
"""Generate comprehensive template documentation"""
|
172
|
+
if not self._validate_templates_folder():
|
173
|
+
return
|
174
|
+
manager = TemplateManager()
|
175
|
+
manager.document_template(template_name, output_file)
|
176
|
+
|
177
|
+
def enhance_template(
|
178
|
+
self,
|
179
|
+
template_name: str = "default",
|
180
|
+
mapping_file: Optional[str] = None,
|
181
|
+
no_backup: bool = False,
|
182
|
+
use_conventions: bool = True,
|
183
|
+
):
|
184
|
+
"""Enhance template with improved placeholder names"""
|
185
|
+
if not self._validate_templates_folder():
|
186
|
+
return
|
187
|
+
manager = TemplateManager()
|
188
|
+
create_backup = not no_backup
|
189
|
+
manager.enhance_template(template_name, mapping_file, create_backup, use_conventions)
|
190
|
+
|
191
|
+
def generate_placeholder_image(
|
192
|
+
self,
|
193
|
+
width: int,
|
194
|
+
height: int,
|
195
|
+
image_id: Optional[int] = None,
|
196
|
+
filter_type: Optional[str] = None,
|
197
|
+
output_file: Optional[str] = None,
|
198
|
+
):
|
199
|
+
"""Generate PlaceKitten placeholder image"""
|
200
|
+
pk = PlaceKitten()
|
201
|
+
|
202
|
+
try:
|
203
|
+
# Generate image with optional parameters
|
204
|
+
image = pk.generate(
|
205
|
+
width=width, height=height, image_id=image_id, filter_type=filter_type
|
206
|
+
)
|
207
|
+
|
208
|
+
# Set output filename
|
209
|
+
if not output_file:
|
210
|
+
filter_suffix = f"_{filter_type}" if filter_type else ""
|
211
|
+
id_suffix = f"_id{image_id}" if image_id else ""
|
212
|
+
output_file = f"placeholder_{width}x{height}{id_suffix}{filter_suffix}.jpg"
|
213
|
+
|
214
|
+
# Save image
|
215
|
+
result = image.save(output_file)
|
216
|
+
print(f"✅ Placeholder image generated: {result}")
|
217
|
+
return result
|
218
|
+
|
219
|
+
except Exception as e:
|
220
|
+
print(f"❌ Error generating image: {e}")
|
221
|
+
raise
|
222
|
+
|
223
|
+
def smart_crop_image(
|
224
|
+
self,
|
225
|
+
input_file: str,
|
226
|
+
width: int,
|
227
|
+
height: int,
|
228
|
+
save_steps: bool = False,
|
229
|
+
output_file: Optional[str] = None,
|
230
|
+
):
|
231
|
+
"""Apply smart cropping to an existing image"""
|
232
|
+
try:
|
233
|
+
from placekitten.processor import ImageProcessor
|
234
|
+
except ImportError:
|
235
|
+
from src.placekitten.processor import ImageProcessor
|
236
|
+
|
237
|
+
input_path = Path(input_file)
|
238
|
+
if not input_path.exists():
|
239
|
+
raise FileNotFoundError(f"Input image not found: {input_file}")
|
240
|
+
|
241
|
+
try:
|
242
|
+
processor = ImageProcessor(str(input_path))
|
243
|
+
|
244
|
+
# Apply smart cropping
|
245
|
+
result_processor = processor.smart_crop(
|
246
|
+
width=width, height=height, save_steps=save_steps, output_prefix="smart_crop"
|
247
|
+
)
|
248
|
+
|
249
|
+
# Set output filename
|
250
|
+
if not output_file:
|
251
|
+
output_file = f"smart_cropped_{width}x{height}_{input_path.name}"
|
252
|
+
|
253
|
+
# Save result
|
254
|
+
result = result_processor.save(output_file)
|
255
|
+
print(f"✅ Smart crop completed: {result}")
|
256
|
+
|
257
|
+
if save_steps:
|
258
|
+
print("📁 Processing steps saved with 'smart_crop_' prefix")
|
259
|
+
|
260
|
+
return result
|
261
|
+
|
262
|
+
except Exception as e:
|
263
|
+
print(f"❌ Error processing image: {e}")
|
264
|
+
raise
|
265
|
+
|
266
|
+
def list_templates(self):
|
267
|
+
"""List available templates"""
|
268
|
+
if not self._validate_templates_folder():
|
269
|
+
return
|
270
|
+
|
271
|
+
templates = self._get_available_templates()
|
272
|
+
if templates:
|
273
|
+
print("📋 Available templates:")
|
274
|
+
for template in templates:
|
275
|
+
print(f" • {template}")
|
276
|
+
else:
|
277
|
+
print("❌ No templates found in template folder")
|
278
|
+
print("💡 Run 'deckbuilder init' to copy default template files")
|
279
|
+
|
280
|
+
def init_templates(self, path: str = "./templates"):
|
281
|
+
"""Initialize template folder with default files and provide setup guidance"""
|
282
|
+
import shutil
|
283
|
+
|
284
|
+
target_path = Path(path).resolve()
|
285
|
+
|
286
|
+
# Create template folder
|
287
|
+
target_path.mkdir(parents=True, exist_ok=True)
|
288
|
+
|
289
|
+
try:
|
290
|
+
# Find the package assets (development structure)
|
291
|
+
assets_path = Path(__file__).parent.parent.parent / "assets" / "templates"
|
292
|
+
if assets_path.exists():
|
293
|
+
source_pptx = assets_path / "default.pptx"
|
294
|
+
source_json = assets_path / "default.json"
|
295
|
+
else:
|
296
|
+
print("❌ Could not locate template assets")
|
297
|
+
print("💡 Default templates not found in package")
|
298
|
+
return
|
299
|
+
|
300
|
+
# Copy template files
|
301
|
+
files_copied = []
|
302
|
+
if source_pptx.exists():
|
303
|
+
shutil.copy2(source_pptx, target_path / "default.pptx")
|
304
|
+
files_copied.append("default.pptx")
|
305
|
+
|
306
|
+
if source_json.exists():
|
307
|
+
shutil.copy2(source_json, target_path / "default.json")
|
308
|
+
files_copied.append("default.json")
|
309
|
+
|
310
|
+
if not files_copied:
|
311
|
+
print("❌ No template files found to copy")
|
312
|
+
return
|
313
|
+
|
314
|
+
# Success message
|
315
|
+
print(f"✅ Template folder created at {target_path}")
|
316
|
+
print(f"📁 Copied: {', '.join(files_copied)}")
|
317
|
+
print()
|
318
|
+
|
319
|
+
# Environment variable guidance
|
320
|
+
print("💡 To make this permanent, add to your .bash_profile:")
|
321
|
+
print(f'export DECK_TEMPLATE_FOLDER="{target_path}"')
|
322
|
+
print(f'export DECK_OUTPUT_FOLDER="{target_path.parent}"')
|
323
|
+
print('export DECK_TEMPLATE_NAME="default"')
|
324
|
+
print()
|
325
|
+
print("Then reload: source ~/.bash_profile")
|
326
|
+
print()
|
327
|
+
print("🚀 Ready to use! Try: deckbuilder create example.md")
|
328
|
+
|
329
|
+
except Exception as e:
|
330
|
+
print(f"❌ Error setting up templates: {e}")
|
331
|
+
print("💡 Make sure you have write permissions to the target directory")
|
332
|
+
|
333
|
+
def get_config(self):
|
334
|
+
"""Display current configuration"""
|
335
|
+
print("🔧 Deckbuilder Configuration:")
|
336
|
+
print(f" Template Folder: {os.getenv('DECK_TEMPLATE_FOLDER', 'Not set')}")
|
337
|
+
print(f" Output Folder: {os.getenv('DECK_OUTPUT_FOLDER', 'Not set')}")
|
338
|
+
print(f" Default Template: {os.getenv('DECK_TEMPLATE_NAME', 'Not set')}")
|
339
|
+
|
340
|
+
def show_completion_help(self):
|
341
|
+
"""Show tab completion installation instructions"""
|
342
|
+
print("🔧 Tab Completion Setup")
|
343
|
+
print()
|
344
|
+
print("To enable tab completion for deckbuilder commands:")
|
345
|
+
print()
|
346
|
+
print("1. Download the completion script:")
|
347
|
+
completion_url = (
|
348
|
+
"https://raw.githubusercontent.com/teknologika/deckbuilder/main/"
|
349
|
+
"deckbuilder-completion.bash"
|
350
|
+
)
|
351
|
+
print(f" curl -o ~/.deckbuilder-completion.bash {completion_url}")
|
352
|
+
print()
|
353
|
+
print("2. Add to your .bash_profile:")
|
354
|
+
print(' echo "source ~/.deckbuilder-completion.bash" >> ~/.bash_profile')
|
355
|
+
print()
|
356
|
+
print("3. Reload your shell:")
|
357
|
+
print(" source ~/.bash_profile")
|
358
|
+
print()
|
359
|
+
print("✨ After setup, you can use TAB to complete:")
|
360
|
+
print(" • Commands: deckbuilder <TAB>")
|
361
|
+
print(" • Template names: deckbuilder analyze <TAB>")
|
362
|
+
print(" • File paths: deckbuilder create <TAB>")
|
363
|
+
print(" • Global flags: deckbuilder -<TAB>")
|
364
|
+
print()
|
365
|
+
print("For system-wide installation:")
|
366
|
+
print(" sudo curl -o /etc/bash_completion.d/deckbuilder")
|
367
|
+
print(f" {completion_url}")
|
368
|
+
|
369
|
+
|
370
|
+
def create_parser():
|
371
|
+
"""Create command-line argument parser"""
|
372
|
+
parser = argparse.ArgumentParser(
|
373
|
+
prog="deckbuilder",
|
374
|
+
description="Deckbuilder CLI - Intelligent PowerPoint presentation generation",
|
375
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
376
|
+
epilog="""
|
377
|
+
Examples:
|
378
|
+
# Create presentation from markdown
|
379
|
+
deckbuilder create presentation.md
|
380
|
+
deckbuilder -t ~/templates -o ~/output create slides.md
|
381
|
+
|
382
|
+
# Template management
|
383
|
+
deckbuilder analyze default --verbose
|
384
|
+
deckbuilder validate default
|
385
|
+
deckbuilder document default --output template_docs.md
|
386
|
+
|
387
|
+
# Image generation
|
388
|
+
deckbuilder image 800 600 --filter grayscale --output placeholder.jpg
|
389
|
+
deckbuilder crop input.jpg 1920 1080 --save-steps
|
390
|
+
|
391
|
+
# Configuration and setup
|
392
|
+
deckbuilder config
|
393
|
+
deckbuilder templates
|
394
|
+
deckbuilder completion
|
395
|
+
deckbuilder init
|
396
|
+
""",
|
397
|
+
)
|
398
|
+
|
399
|
+
# Global arguments (apply to all commands)
|
400
|
+
parser.add_argument(
|
401
|
+
"-t", "--templates", metavar="PATH", help="Template folder path (default: ./templates/)"
|
402
|
+
)
|
403
|
+
parser.add_argument(
|
404
|
+
"-o", "--output", metavar="PATH", help="Output folder path (default: current directory)"
|
405
|
+
)
|
406
|
+
|
407
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
408
|
+
|
409
|
+
# Create presentation command
|
410
|
+
create_parser = subparsers.add_parser(
|
411
|
+
"create", help="Create presentation from markdown or JSON file"
|
412
|
+
)
|
413
|
+
create_parser.add_argument("input_file", help="Input markdown (.md) or JSON (.json) file")
|
414
|
+
create_parser.add_argument("--output", "-o", help="Output filename (without extension)")
|
415
|
+
create_parser.add_argument("--template", "-t", help="Template name to use (default: 'default')")
|
416
|
+
|
417
|
+
# Template analysis commands
|
418
|
+
analyze_parser = subparsers.add_parser("analyze", help="Analyze template structure")
|
419
|
+
analyze_parser.add_argument("template", nargs="?", default="default", help="Template name")
|
420
|
+
analyze_parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
421
|
+
|
422
|
+
validate_parser = subparsers.add_parser("validate", help="Validate template and mappings")
|
423
|
+
validate_parser.add_argument("template", nargs="?", default="default", help="Template name")
|
424
|
+
|
425
|
+
document_parser = subparsers.add_parser("document", help="Generate template documentation")
|
426
|
+
document_parser.add_argument("template", nargs="?", default="default", help="Template name")
|
427
|
+
document_parser.add_argument("--output", "-o", help="Output documentation file")
|
428
|
+
|
429
|
+
enhance_parser = subparsers.add_parser("enhance", help="Enhance template placeholders")
|
430
|
+
enhance_parser.add_argument("template", nargs="?", default="default", help="Template name")
|
431
|
+
enhance_parser.add_argument("--mapping", help="Custom mapping file")
|
432
|
+
enhance_parser.add_argument("--no-backup", action="store_true", help="Skip backup creation")
|
433
|
+
enhance_parser.add_argument(
|
434
|
+
"--no-conventions",
|
435
|
+
action="store_false",
|
436
|
+
dest="use_conventions",
|
437
|
+
help="Don't use naming conventions",
|
438
|
+
)
|
439
|
+
|
440
|
+
# PlaceKitten image generation
|
441
|
+
image_parser = subparsers.add_parser("image", help="Generate placeholder image")
|
442
|
+
image_parser.add_argument("width", type=int, help="Image width")
|
443
|
+
image_parser.add_argument("height", type=int, help="Image height")
|
444
|
+
image_parser.add_argument("--id", type=int, help="Specific kitten image ID (1-6)")
|
445
|
+
image_parser.add_argument("--filter", help="Filter to apply (grayscale, sepia, blur, etc.)")
|
446
|
+
image_parser.add_argument("--output", "-o", help="Output filename")
|
447
|
+
|
448
|
+
# Smart crop command
|
449
|
+
crop_parser = subparsers.add_parser("crop", help="Apply smart cropping to image")
|
450
|
+
crop_parser.add_argument("input_file", help="Input image file")
|
451
|
+
crop_parser.add_argument("width", type=int, help="Target width")
|
452
|
+
crop_parser.add_argument("height", type=int, help="Target height")
|
453
|
+
crop_parser.add_argument("--save-steps", action="store_true", help="Save processing steps")
|
454
|
+
crop_parser.add_argument("--output", "-o", help="Output filename")
|
455
|
+
|
456
|
+
# Configuration commands
|
457
|
+
subparsers.add_parser("config", help="Show current configuration")
|
458
|
+
subparsers.add_parser("templates", help="List available templates")
|
459
|
+
subparsers.add_parser("completion", help="Show tab completion setup instructions")
|
460
|
+
|
461
|
+
# Init command
|
462
|
+
init_parser = subparsers.add_parser(
|
463
|
+
"init", help="Initialize template folder with default files"
|
464
|
+
)
|
465
|
+
init_parser.add_argument(
|
466
|
+
"path", nargs="?", default="./templates", help="Template folder path (default: ./templates)"
|
467
|
+
)
|
468
|
+
|
469
|
+
return parser
|
470
|
+
|
471
|
+
|
472
|
+
def main():
|
473
|
+
"""Main CLI entry point"""
|
474
|
+
parser = create_parser()
|
475
|
+
args = parser.parse_args()
|
476
|
+
|
477
|
+
if not args.command:
|
478
|
+
parser.print_help()
|
479
|
+
return
|
480
|
+
|
481
|
+
# Initialize CLI with global arguments
|
482
|
+
cli = DeckbuilderCLI(templates_path=args.templates, output_path=args.output)
|
483
|
+
|
484
|
+
try:
|
485
|
+
# Route commands
|
486
|
+
if args.command == "create":
|
487
|
+
cli.create_presentation(
|
488
|
+
input_file=args.input_file, output_name=args.output, template=args.template
|
489
|
+
)
|
490
|
+
|
491
|
+
elif args.command == "analyze":
|
492
|
+
cli.analyze_template(args.template, verbose=args.verbose)
|
493
|
+
|
494
|
+
elif args.command == "validate":
|
495
|
+
cli.validate_template(args.template)
|
496
|
+
|
497
|
+
elif args.command == "document":
|
498
|
+
cli.document_template(args.template, args.output)
|
499
|
+
|
500
|
+
elif args.command == "enhance":
|
501
|
+
cli.enhance_template(
|
502
|
+
template_name=args.template,
|
503
|
+
mapping_file=args.mapping,
|
504
|
+
no_backup=args.no_backup,
|
505
|
+
use_conventions=args.use_conventions,
|
506
|
+
)
|
507
|
+
|
508
|
+
elif args.command == "image":
|
509
|
+
cli.generate_placeholder_image(
|
510
|
+
width=args.width,
|
511
|
+
height=args.height,
|
512
|
+
image_id=args.id,
|
513
|
+
filter_type=args.filter,
|
514
|
+
output_file=args.output,
|
515
|
+
)
|
516
|
+
|
517
|
+
elif args.command == "crop":
|
518
|
+
cli.smart_crop_image(
|
519
|
+
input_file=args.input_file,
|
520
|
+
width=args.width,
|
521
|
+
height=args.height,
|
522
|
+
save_steps=args.save_steps,
|
523
|
+
output_file=args.output,
|
524
|
+
)
|
525
|
+
|
526
|
+
elif args.command == "config":
|
527
|
+
cli.get_config()
|
528
|
+
|
529
|
+
elif args.command == "templates":
|
530
|
+
cli.list_templates()
|
531
|
+
|
532
|
+
elif args.command == "completion":
|
533
|
+
cli.show_completion_help()
|
534
|
+
|
535
|
+
elif args.command == "init":
|
536
|
+
cli.init_templates(args.path)
|
537
|
+
|
538
|
+
except Exception as e:
|
539
|
+
print(f"❌ Command failed: {e}")
|
540
|
+
sys.exit(1)
|
541
|
+
|
542
|
+
|
543
|
+
if __name__ == "__main__":
|
544
|
+
main()
|