# artifact-ai - Generate or modify artifacts using AI # Usage: cat diagram.puml | artifact-ai --format plantuml --instruction "Add a cache layer" # Usage: echo "" | artifact-ai --format mermaid --instruction "Create a flowchart for login" name: artifact-ai description: Generate or modify artifacts (diagrams, 3D models, code) using AI category: Artifact arguments: - flag: --format variable: format required: true description: "Artifact format: plantuml, mermaid, openscad, svg, excalidraw, code" - flag: --instruction variable: instruction required: true description: Natural language instruction for generating or modifying the artifact output: "{result}" steps: # Step 1: Build format-specific prompt and call AI - type: code output_var: prompt code: | import json format_prompts = { 'plantuml': """You are a PlantUML expert. Create or modify PlantUML diagrams. Supported diagram types: - Sequence diagrams: actor, participant, ->, --> - Class diagrams: class, interface, extends, implements - Activity diagrams: start, stop, :action;, if/then/else - Component diagrams: component, package, [interface] - State diagrams: state, [*], --> Always wrap with @startuml/@enduml.""", 'mermaid': """You are a Mermaid diagram expert. Create or modify Mermaid diagrams. Supported diagram types: - Flowcharts: graph TD/LR, A-->B - Sequence: sequenceDiagram, Alice->>Bob - Class: classDiagram, class ClassName - State: stateDiagram-v2 - Entity Relationship: erDiagram - Gantt: gantt, section, task Start with the diagram type declaration (graph TD, sequenceDiagram, etc.).""", 'openscad': """You are an OpenSCAD 3D modeling expert. Create or modify parametric 3D models. Core operations: - Primitives: cube(), sphere(), cylinder(), polyhedron() - Transformations: translate(), rotate(), scale(), mirror() - Boolean: union(), difference(), intersection() - 2D: circle(), square(), polygon(), linear_extrude(), rotate_extrude() Use modules for reusable components. Use $fn for smoothness.""", 'svg': """You are an SVG expert. Create or modify vector graphics. Core elements: - Shapes: , , , , , , - Text: , - Groups: , , , - Styling: fill, stroke, stroke-width, opacity, transform Include xmlns and viewBox. Use descriptive IDs.""", 'excalidraw': """You are an Excalidraw expert. Create hand-drawn style diagrams as JSON. Structure: - type: element type (rectangle, diamond, ellipse, arrow, line, text) - x, y: position - width, height: dimensions - strokeColor, backgroundColor, fillStyle - roughness: 0-2 (0=smooth, 2=sketchy) Output valid JSON with "type": "excalidraw" and "elements" array.""", 'code': """You are a programming expert. Create or modify code in any language. Focus on: - Clean, readable code - Proper language conventions - Comments for complex logic only - Error handling where appropriate Detect the language from context or instruction.""" } format_outputs = { 'plantuml': "Output ONLY valid PlantUML code starting with @startuml and ending with @enduml.", 'mermaid': "Output ONLY valid Mermaid code starting with the diagram type.", 'openscad': "Output ONLY valid OpenSCAD code with proper syntax.", 'svg': "Output ONLY valid SVG code starting with .", 'excalidraw': 'Output ONLY valid Excalidraw JSON starting with { and ending with }.', 'code': "Output ONLY the code, no markdown fences or explanations." } current_code = input.strip() if input.strip() else "(empty - create from scratch)" format_guide = format_prompts.get(format, f"You are a {format} expert.") output_instruction = format_outputs.get(format, "Output ONLY the code, no explanations.") prompt = f"""{format_guide} CRITICAL: {output_instruction} Current code: --- {current_code} --- User request: {instruction} {output_instruction}""" # Don't print - just set the variable result = prompt - type: prompt prompt: "{prompt}" provider: opencode-deepseek output_var: ai_output # Step 2: Clean up output based on format - type: code output_var: result code: | import re code = ai_output.strip() # Remove markdown code blocks if present (three backticks) fence_pattern = r'`{3}(?:\w+)?\n(.*?)`{3}' match = re.search(fence_pattern, code, re.DOTALL) if match: code = match.group(1).strip() # Format-specific cleanup if format == 'svg': # Remove any wrapper formats code = re.sub(r'^@startuml\s*\n?', '', code) code = re.sub(r'\n?@enduml\s*$', '', code) # Find SVG content svg_match = re.search(r'(<\?xml[^?]*\?>)?\s*()', code) if svg_match: xml_decl = svg_match.group(1) or '' svg_content = svg_match.group(2) code = (xml_decl + '\n' + svg_content).strip() if xml_decl else svg_content elif format == 'excalidraw': # Find JSON object json_match = re.search(r'\{[\s\S]*\}', code) if json_match: code = json_match.group(0) elif format == 'plantuml': # Extract first complete @startuml...@enduml block (handles AI outputting duplicates) puml_match = re.search(r'(@start\w+.*?@end\w+)', code, re.DOTALL) if puml_match: code = puml_match.group(1) else: # No complete block found - ensure proper tags if not code.strip().startswith('@start'): code = '@startuml\n' + code if not code.strip().endswith('@enduml') and '@enduml' not in code: code = code + '\n@enduml' elif format == 'mermaid': # Extract first complete mermaid diagram (handles duplicates) # Mermaid starts with diagram type declaration mermaid_types = r'(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|journey)' mermaid_match = re.search(rf'({mermaid_types}[\s\S]*?)(?={mermaid_types}|\Z)', code) if mermaid_match: code = mermaid_match.group(1).strip() result = code.strip()