llm-ide-rules 0.2.0__tar.gz → 0.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llm-ide-rules
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: CLI tool for managing LLM IDE prompts and rules
5
5
  Keywords: llm,ide,prompts,cursor,copilot
6
6
  Author: Michael Bianco
@@ -12,7 +12,7 @@ Requires-Python: >=3.9
12
12
  Project-URL: Repository, https://github.com/iloveitaly/llm-ide-rules
13
13
  Description-Content-Type: text/markdown
14
14
 
15
- # Copilot & Cursor LLM Instructions
15
+ # Copilot, Cursor, Claude, Gemini, etc LLM Instructions
16
16
 
17
17
  Going to try to centralize all my prompts in a single place and create some scripts to help convert from copilot to cursor, etc.
18
18
 
@@ -1,4 +1,4 @@
1
- # Copilot & Cursor LLM Instructions
1
+ # Copilot, Cursor, Claude, Gemini, etc LLM Instructions
2
2
 
3
3
  Going to try to centralize all my prompts in a single place and create some scripts to help convert from copilot to cursor, etc.
4
4
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llm-ide-rules"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "CLI tool for managing LLM IDE prompts and rules"
5
5
  keywords = ["llm", "ide", "prompts", "cursor", "copilot"]
6
6
  readme = "README.md"
@@ -11,7 +11,7 @@ urls = { "Repository" = "https://github.com/iloveitaly/llm-ide-rules" }
11
11
 
12
12
  # additional packaging information: https://packaging.python.org/en/latest/specifications/core-metadata/#license
13
13
  [project.scripts]
14
- llm_ide_rules = "llm_ide_rules:main"
14
+ llm-ide-rules = "llm_ide_rules:main"
15
15
 
16
16
  [build-system]
17
17
  requires = ["uv_build>=0.8.11,<0.9.0"]
@@ -7,7 +7,7 @@ from llm_ide_rules.commands.explode import explode_main
7
7
  from llm_ide_rules.commands.implode import cursor, github
8
8
  from llm_ide_rules.commands.download import download_main
9
9
 
10
- __version__ = "0.2.0"
10
+ __version__ = "0.3.0"
11
11
 
12
12
  app = typer.Typer(
13
13
  name="llm_ide_rules",
@@ -1,6 +1,7 @@
1
1
  """Download command: Download LLM instruction files from GitHub repositories."""
2
2
 
3
3
  import logging
4
+ import re
4
5
  import tempfile
5
6
  import zipfile
6
7
  from pathlib import Path
@@ -13,9 +14,31 @@ from typing_extensions import Annotated
13
14
 
14
15
  logger = structlog.get_logger()
15
16
 
16
- DEFAULT_REPO = "iloveitaly/llm_ide_rules"
17
+ DEFAULT_REPO = "iloveitaly/llm-ide-rules"
17
18
  DEFAULT_BRANCH = "master"
18
19
 
20
+
21
+ def normalize_repo(repo: str) -> str:
22
+ """Normalize repository input to user/repo format.
23
+
24
+ Handles both formats:
25
+ - user/repo (unchanged)
26
+ - https://github.com/user/repo/ (extracts user/repo)
27
+ """
28
+ # If it's already in user/repo format, return as-is
29
+ if "/" in repo and not repo.startswith("http"):
30
+ return repo
31
+
32
+ # Extract user/repo from GitHub URL
33
+ github_pattern = r"https?://github\.com/([^/]+/[^/]+)/?.*"
34
+ match = re.match(github_pattern, repo)
35
+
36
+ if match:
37
+ return match.group(1)
38
+
39
+ # If no pattern matches, assume it's already in the correct format
40
+ return repo
41
+
19
42
  # Define what files/directories each instruction type includes
20
43
  INSTRUCTION_TYPES = {
21
44
  "cursor": {"directories": [".cursor"], "files": []},
@@ -27,6 +50,7 @@ INSTRUCTION_TYPES = {
27
50
  "gemini": {"directories": [], "files": ["GEMINI.md"]},
28
51
  "claude": {"directories": [], "files": ["CLAUDE.md"]},
29
52
  "agent": {"directories": [], "files": ["AGENT.md"]},
53
+ "agents": {"directories": [], "files": [], "recursive_files": ["AGENTS.md"]},
30
54
  }
31
55
 
32
56
  # Default types to download when no specific types are specified
@@ -35,9 +59,10 @@ DEFAULT_TYPES = list(INSTRUCTION_TYPES.keys())
35
59
 
36
60
  def download_and_extract_repo(repo: str, branch: str = DEFAULT_BRANCH) -> Path:
37
61
  """Download a GitHub repository as a ZIP and extract it to a temporary directory."""
38
- zip_url = f"https://github.com/{repo}/archive/{branch}.zip"
62
+ normalized_repo = normalize_repo(repo)
63
+ zip_url = f"https://github.com/{normalized_repo}/archive/{branch}.zip"
39
64
 
40
- logger.info("Downloading repository", repo=repo, branch=branch, url=zip_url)
65
+ logger.info("Downloading repository", repo=repo, normalized_repo=normalized_repo, branch=branch, url=zip_url)
41
66
 
42
67
  try:
43
68
  response = requests.get(zip_url, timeout=30)
@@ -123,6 +148,60 @@ def copy_instruction_files(
123
148
  target_file.write_bytes(source_file.read_bytes())
124
149
  copied_items.append(file_name)
125
150
 
151
+ # Copy recursive files (search throughout repository)
152
+ for file_pattern in config.get("recursive_files", []):
153
+ copied_recursive = copy_recursive_files(repo_dir, target_dir, file_pattern)
154
+ copied_items.extend(copied_recursive)
155
+
156
+ return copied_items
157
+
158
+
159
+ def copy_recursive_files(
160
+ repo_dir: Path, target_dir: Path, file_pattern: str
161
+ ) -> List[str]:
162
+ """Recursively copy files matching pattern, preserving directory structure.
163
+
164
+ Only copies files to locations where the target directory already exists.
165
+ Warns and skips files where target directories don't exist.
166
+
167
+ Args:
168
+ repo_dir: Source repository directory
169
+ target_dir: Target directory to copy to
170
+ file_pattern: File pattern to search for (e.g., "AGENTS.md")
171
+
172
+ Returns:
173
+ List of copied file paths relative to target_dir
174
+ """
175
+ copied_items = []
176
+
177
+ # Find all matching files recursively
178
+ matching_files = list(repo_dir.rglob(file_pattern))
179
+
180
+ for source_file in matching_files:
181
+ # Calculate relative path from repo root
182
+ relative_path = source_file.relative_to(repo_dir)
183
+ target_file = target_dir / relative_path
184
+
185
+ # Check if target directory already exists
186
+ target_parent = target_file.parent
187
+ if not target_parent.exists():
188
+ logger.warning(
189
+ "Target directory does not exist, skipping file copy",
190
+ target_directory=str(target_parent),
191
+ file=str(relative_path)
192
+ )
193
+ continue
194
+
195
+ logger.info(
196
+ "Copying recursive file",
197
+ source=str(source_file),
198
+ target=str(target_file)
199
+ )
200
+
201
+ # Copy file (parent directory already exists)
202
+ target_file.write_bytes(source_file.read_bytes())
203
+ copied_items.append(str(relative_path))
204
+
126
205
  return copied_items
127
206
 
128
207
 
@@ -161,7 +240,7 @@ def download_main(
161
240
  instruction_types: Annotated[
162
241
  List[str],
163
242
  typer.Argument(
164
- help="Types of instructions to download (cursor, github, gemini, claude, agent). Downloads everything by default."
243
+ help="Types of instructions to download (cursor, github, gemini, claude, agent, agents). Downloads everything by default."
165
244
  ),
166
245
  ] = None,
167
246
  repo: Annotated[