llm-ide-rules 0.2.1__tar.gz → 0.4.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.1
3
+ Version: 0.4.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
 
@@ -62,6 +62,10 @@ uvx llm-ide-rules download [instruction_types] # Download everything by defau
62
62
  uvx llm-ide-rules download cursor github # Download specific types
63
63
  uvx llm-ide-rules download --repo other/repo # Download from different repo
64
64
 
65
+ # Delete downloaded instruction files
66
+ uvx llm-ide-rules delete [instruction_types] # Delete everything by default
67
+ uvx llm-ide-rules delete cursor gemini # Delete specific types
68
+ uvx llm-ide-rules delete --yes # Skip confirmation prompt
65
69
 
66
70
  ```
67
71
 
@@ -85,8 +89,28 @@ uvx llm-ide-rules download cursor github
85
89
 
86
90
  # Download from a different repository
87
91
  uvx llm-ide-rules download --repo other-user/other-repo --target ./my-project
92
+
93
+ # Delete all downloaded files (with confirmation)
94
+ uvx llm-ide-rules delete
95
+
96
+ # Delete specific instruction types
97
+ uvx llm-ide-rules delete cursor gemini --target ./my-project
98
+
99
+ # Delete without confirmation prompt
100
+ uvx llm-ide-rules delete --yes
88
101
  ```
89
102
 
103
+ ### IDE Command Format Comparison
104
+
105
+ Different AI coding assistants use different formats for commands:
106
+
107
+ | IDE | Directory | Format | Notes |
108
+ |-----|-----------|--------|-------|
109
+ | **Cursor** | `.cursor/commands/` | `.md` (plain markdown) | Simple, no frontmatter |
110
+ | **Claude Code** | `.claude/commands/` | `.md` (plain markdown) | Simple, no frontmatter |
111
+ | **GitHub Copilot** | `.github/prompts/` | `.prompt.md` (YAML + markdown) | Requires frontmatter with `mode: 'agent'` |
112
+ | **Gemini CLI** | `.gemini/commands/` | `.toml` | Uses TOML format, supports `{{args}}` and shell commands |
113
+
90
114
  ## Development
91
115
 
92
116
  ### Using the CLI for Development
@@ -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
 
@@ -48,6 +48,10 @@ uvx llm-ide-rules download [instruction_types] # Download everything by defau
48
48
  uvx llm-ide-rules download cursor github # Download specific types
49
49
  uvx llm-ide-rules download --repo other/repo # Download from different repo
50
50
 
51
+ # Delete downloaded instruction files
52
+ uvx llm-ide-rules delete [instruction_types] # Delete everything by default
53
+ uvx llm-ide-rules delete cursor gemini # Delete specific types
54
+ uvx llm-ide-rules delete --yes # Skip confirmation prompt
51
55
 
52
56
  ```
53
57
 
@@ -71,8 +75,28 @@ uvx llm-ide-rules download cursor github
71
75
 
72
76
  # Download from a different repository
73
77
  uvx llm-ide-rules download --repo other-user/other-repo --target ./my-project
78
+
79
+ # Delete all downloaded files (with confirmation)
80
+ uvx llm-ide-rules delete
81
+
82
+ # Delete specific instruction types
83
+ uvx llm-ide-rules delete cursor gemini --target ./my-project
84
+
85
+ # Delete without confirmation prompt
86
+ uvx llm-ide-rules delete --yes
74
87
  ```
75
88
 
89
+ ### IDE Command Format Comparison
90
+
91
+ Different AI coding assistants use different formats for commands:
92
+
93
+ | IDE | Directory | Format | Notes |
94
+ |-----|-----------|--------|-------|
95
+ | **Cursor** | `.cursor/commands/` | `.md` (plain markdown) | Simple, no frontmatter |
96
+ | **Claude Code** | `.claude/commands/` | `.md` (plain markdown) | Simple, no frontmatter |
97
+ | **GitHub Copilot** | `.github/prompts/` | `.prompt.md` (YAML + markdown) | Requires frontmatter with `mode: 'agent'` |
98
+ | **Gemini CLI** | `.gemini/commands/` | `.toml` | Uses TOML format, supports `{{args}}` and shell commands |
99
+
76
100
  ## Development
77
101
 
78
102
  ### Using the CLI for Development
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "llm-ide-rules"
3
- version = "0.2.1"
3
+ version = "0.4.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"
@@ -6,8 +6,9 @@ from typing_extensions import Annotated
6
6
  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
+ from llm_ide_rules.commands.delete import delete_main
9
10
 
10
- __version__ = "0.2.1"
11
+ __version__ = "0.4.0"
11
12
 
12
13
  app = typer.Typer(
13
14
  name="llm_ide_rules",
@@ -18,6 +19,7 @@ app = typer.Typer(
18
19
  # Add commands directly
19
20
  app.command("explode", help="Convert instruction file to separate rule files")(explode_main)
20
21
  app.command("download", help="Download LLM instruction files from GitHub repositories")(download_main)
22
+ app.command("delete", help="Remove downloaded LLM instruction files")(delete_main)
21
23
 
22
24
  # Create implode sub-typer
23
25
  implode_app = typer.Typer(help="Bundle rule files into a single instruction file")
@@ -0,0 +1,179 @@
1
+ """Delete command: Remove downloaded LLM instruction files."""
2
+
3
+ import logging
4
+ import shutil
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ import structlog
9
+ import typer
10
+ from typing_extensions import Annotated
11
+
12
+ from llm_ide_rules.commands.download import INSTRUCTION_TYPES, DEFAULT_TYPES
13
+
14
+ logger = structlog.get_logger()
15
+
16
+
17
+ def find_files_to_delete(
18
+ instruction_types: List[str], target_dir: Path
19
+ ) -> tuple[List[Path], List[Path]]:
20
+ """Find all files and directories that would be deleted.
21
+
22
+ Returns:
23
+ Tuple of (directories, files) to delete
24
+ """
25
+ dirs_to_delete = []
26
+ files_to_delete = []
27
+
28
+ for inst_type in instruction_types:
29
+ if inst_type not in INSTRUCTION_TYPES:
30
+ logger.warning("Unknown instruction type", type=inst_type)
31
+ continue
32
+
33
+ config = INSTRUCTION_TYPES[inst_type]
34
+
35
+ for dir_name in config["directories"]:
36
+ dir_path = target_dir / dir_name
37
+ if dir_path.exists() and dir_path.is_dir():
38
+ dirs_to_delete.append(dir_path)
39
+
40
+ for file_name in config["files"]:
41
+ file_path = target_dir / file_name
42
+ if file_path.exists() and file_path.is_file():
43
+ files_to_delete.append(file_path)
44
+
45
+ for file_pattern in config.get("recursive_files", []):
46
+ matching_files = list(target_dir.rglob(file_pattern))
47
+ files_to_delete.extend([f for f in matching_files if f.is_file()])
48
+
49
+ return dirs_to_delete, files_to_delete
50
+
51
+
52
+ def delete_main(
53
+ instruction_types: Annotated[
54
+ List[str],
55
+ typer.Argument(
56
+ help="Types of instructions to delete (cursor, github, gemini, claude, agent, agents). Deletes everything by default."
57
+ ),
58
+ ] = None,
59
+ target_dir: Annotated[
60
+ str, typer.Option("--target", "-t", help="Target directory to delete from")
61
+ ] = ".",
62
+ yes: Annotated[
63
+ bool,
64
+ typer.Option("--yes", "-y", help="Skip confirmation prompt and delete immediately"),
65
+ ] = False,
66
+ verbose: Annotated[
67
+ bool, typer.Option("--verbose", "-v", help="Enable verbose logging")
68
+ ] = False,
69
+ ):
70
+ """Remove downloaded LLM instruction files.
71
+
72
+ This command removes files and directories that were downloaded by the 'download' command.
73
+ It will show you what will be deleted and ask for confirmation before proceeding.
74
+
75
+ Examples:
76
+
77
+ \b
78
+ # Delete everything (with confirmation)
79
+ llm_ide_rules delete
80
+
81
+ \b
82
+ # Delete only Cursor and Gemini files
83
+ llm_ide_rules delete cursor gemini
84
+
85
+ \b
86
+ # Delete without confirmation prompt
87
+ llm_ide_rules delete --yes
88
+
89
+ \b
90
+ # Delete from a specific directory
91
+ llm_ide_rules delete --target ./my-project
92
+ """
93
+ if verbose:
94
+ logging.basicConfig(level=logging.DEBUG)
95
+ structlog.configure(
96
+ wrapper_class=structlog.make_filtering_bound_logger(logging.DEBUG),
97
+ )
98
+
99
+ if not instruction_types:
100
+ instruction_types = DEFAULT_TYPES
101
+
102
+ invalid_types = [t for t in instruction_types if t not in INSTRUCTION_TYPES]
103
+ if invalid_types:
104
+ logger.error(
105
+ "Invalid instruction types",
106
+ invalid_types=invalid_types,
107
+ valid_types=list(INSTRUCTION_TYPES.keys()),
108
+ )
109
+ raise typer.Exit(1)
110
+
111
+ target_path = Path(target_dir).resolve()
112
+
113
+ if not target_path.exists():
114
+ logger.error("Target directory does not exist", target_dir=str(target_path))
115
+ typer.echo(f"Error: Target directory does not exist: {target_path}")
116
+ raise typer.Exit(1)
117
+
118
+ logger.info(
119
+ "Finding files to delete",
120
+ instruction_types=instruction_types,
121
+ target_dir=str(target_path),
122
+ )
123
+
124
+ dirs_to_delete, files_to_delete = find_files_to_delete(
125
+ instruction_types, target_path
126
+ )
127
+
128
+ if not dirs_to_delete and not files_to_delete:
129
+ logger.info("No files found to delete")
130
+ typer.echo("No matching instruction files found to delete.")
131
+ return
132
+
133
+ typer.echo("\nThe following files and directories will be deleted:\n")
134
+
135
+ if dirs_to_delete:
136
+ typer.echo("Directories:")
137
+ for dir_path in sorted(dirs_to_delete):
138
+ relative_path = dir_path.relative_to(target_path)
139
+ typer.echo(f" - {relative_path}/")
140
+
141
+ if files_to_delete:
142
+ typer.echo("\nFiles:")
143
+ for file_path in sorted(files_to_delete):
144
+ relative_path = file_path.relative_to(target_path)
145
+ typer.echo(f" - {relative_path}")
146
+
147
+ total_items = len(dirs_to_delete) + len(files_to_delete)
148
+ typer.echo(f"\nTotal: {total_items} items")
149
+
150
+ if not yes:
151
+ typer.echo()
152
+ confirm = typer.confirm("Are you sure you want to delete these files?")
153
+ if not confirm:
154
+ logger.info("Deletion cancelled by user")
155
+ typer.echo("Deletion cancelled.")
156
+ raise typer.Exit(0)
157
+
158
+ deleted_count = 0
159
+
160
+ for dir_path in dirs_to_delete:
161
+ try:
162
+ logger.info("Deleting directory", path=str(dir_path))
163
+ shutil.rmtree(dir_path)
164
+ deleted_count += 1
165
+ except Exception as e:
166
+ logger.error("Failed to delete directory", path=str(dir_path), error=str(e))
167
+ typer.echo(f"Error deleting {dir_path}: {e}", err=True)
168
+
169
+ for file_path in files_to_delete:
170
+ try:
171
+ logger.info("Deleting file", path=str(file_path))
172
+ file_path.unlink()
173
+ deleted_count += 1
174
+ except Exception as e:
175
+ logger.error("Failed to delete file", path=str(file_path), error=str(e))
176
+ typer.echo(f"Error deleting {file_path}: {e}", err=True)
177
+
178
+ logger.info("Deletion completed", deleted_count=deleted_count, total_items=total_items)
179
+ typer.echo(f"\nSuccessfully deleted {deleted_count} of {total_items} items.")
@@ -50,6 +50,7 @@ INSTRUCTION_TYPES = {
50
50
  "gemini": {"directories": [], "files": ["GEMINI.md"]},
51
51
  "claude": {"directories": [], "files": ["CLAUDE.md"]},
52
52
  "agent": {"directories": [], "files": ["AGENT.md"]},
53
+ "agents": {"directories": [], "files": [], "recursive_files": ["AGENTS.md"]},
53
54
  }
54
55
 
55
56
  # Default types to download when no specific types are specified
@@ -147,6 +148,60 @@ def copy_instruction_files(
147
148
  target_file.write_bytes(source_file.read_bytes())
148
149
  copied_items.append(file_name)
149
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
+
150
205
  return copied_items
151
206
 
152
207
 
@@ -185,7 +240,7 @@ def download_main(
185
240
  instruction_types: Annotated[
186
241
  List[str],
187
242
  typer.Argument(
188
- 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."
189
244
  ),
190
245
  ] = None,
191
246
  repo: Annotated[