feat: add initial llm-fragments-repomix plugin

Amolith created

Add plugin that loads repository contents using Repomix:
- Clone git repositories to temporary directories
- Run repomix to generate AI-friendly repository summaries
- Return repomix output as a single fragment
- Clean up temporary directories automatically

Change summary

README.md                | 48 +++++++++++++++++++++++
llm_fragments_repomix.py | 85 ++++++++++++++++++++++++++++++++++++++++++
pyproject.toml           | 26 ++++++++++++
3 files changed, 159 insertions(+)

Detailed changes

README.md 🔗

@@ -0,0 +1,48 @@
+# llm-fragments-repomix
+
+A plugin for [LLM](https://llm.datasette.io/) that loads repository contents as fragments using [Repomix](https://github.com/yamadashy/repomix).
+
+## Installation
+
+First, install the plugin:
+
+```bash
+pip install llm-fragments-repomix
+```
+
+Make sure you have `repomix` installed:
+
+```bash
+npm install -g repomix
+```
+
+## Usage
+
+Use the `repomix:` prefix with a full git repository URL:
+
+```bash
+llm -f repomix:https://git.sr.ht/~amolith/willow "Tell me about this project"
+```
+
+```bash
+llm -f repomix:ssh://git.sr.ht:~amolith/willow "Analyze the code structure"
+```
+
+The plugin will:
+1. Clone the repository to a temporary directory
+2. Run repomix on the cloned repository 
+3. Return the repomix output as a single fragment
+4. Clean up the temporary directory
+
+## Requirements
+
+- Python 3.9+
+- `git` command available in PATH
+- `repomix` command available in PATH
+
+## Future Work
+
+Future versions may support:
+- Passing repomix arguments (e.g., `--compress`, `--ignore`)
+- Integration with other fragment loaders for repository shortcuts
+- Configuration options for repomix behavior

llm_fragments_repomix.py 🔗

@@ -0,0 +1,85 @@
+from typing import List
+import llm
+import os
+import pathlib
+import subprocess
+import tempfile
+import shutil
+
+
+@llm.hookimpl
+def register_fragment_loaders(register):
+    register("repomix", repomix_loader)
+
+
+def repomix_loader(argument: str) -> List[llm.Fragment]:
+    """
+    Load repository contents as fragments using Repomix
+    
+    Argument is a git repository URL (https:// or ssh://)
+    Examples:
+        repomix:https://git.sr.ht/~amolith/willow
+        repomix:ssh://git.sr.ht:~amolith/willow
+    """
+    if not argument.startswith(("https://", "ssh://", "git@")):
+        raise ValueError(
+            f"Repository URL must start with https://, ssh://, or git@ - got: {argument}"
+        )
+    
+    # Check if repomix is available
+    if not shutil.which("repomix"):
+        raise ValueError(
+            "repomix command not found. Please install repomix first: "
+            "https://github.com/yamadashy/repomix"
+        )
+    
+    # Create a temporary directory for the cloned repository
+    with tempfile.TemporaryDirectory() as temp_dir:
+        repo_path = pathlib.Path(temp_dir) / "repo"
+        
+        try:
+            # Clone the repository
+            subprocess.run(
+                ["git", "clone", "--depth=1", argument, str(repo_path)],
+                check=True,
+                capture_output=True,
+                text=True,
+            )
+            
+            # Run repomix on the cloned repository
+            result = subprocess.run(
+                ["repomix", "--stdout", str(repo_path)],
+                check=True,
+                capture_output=True,
+                text=True,
+            )
+            
+            # Create a single fragment with the repomix output
+            fragments = [
+                llm.Fragment(
+                    content=result.stdout,
+                    source=f"repomix:{argument}"
+                )
+            ]
+            
+            return fragments
+            
+        except subprocess.CalledProcessError as e:
+            # Handle Git or repomix errors
+            if "git" in str(e.cmd):
+                raise ValueError(
+                    f"Failed to clone repository {argument}: {e.stderr}"
+                )
+            elif "repomix" in str(e.cmd):
+                raise ValueError(
+                    f"Failed to run repomix on {argument}: {e.stderr}"
+                )
+            else:
+                raise ValueError(
+                    f"Command failed: {e.stderr}"
+                )
+        except Exception as e:
+            # Handle other errors
+            raise ValueError(
+                f"Error processing repository {argument}: {str(e)}"
+            )

pyproject.toml 🔗

@@ -0,0 +1,26 @@
+[project]
+name = "llm-fragments-repomix"
+version = "0.1.0"
+description = "Load repository contents as LLM fragments using Repomix"
+readme = "README.md"
+authors = [{name = "Amolith"}]
+license = "Apache-2.0"
+requires-python = ">=3.9"
+dependencies = [
+    "llm"
+]
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project.urls]
+Homepage = "https://git.sr.ht/~amolith/llm-fragments-repomix"
+Changelog = "https://git.sr.ht/~amolith/llm-fragments-repomix/refs"
+Issues = "https://todo.sr.ht/~amolith/public-tracker"
+
+[project.entry-points.llm]
+fragments_repomix = "llm_fragments_repomix"
+
+[project.optional-dependencies]
+test = ["pytest"]