kopipasta 0.1.0__py3-none-any.whl → 0.3.0__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.

Potentially problematic release.


This version of kopipasta might be problematic. Click here for more details.

kopipasta/main.py CHANGED
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env python3
2
2
  import os
3
3
  import argparse
4
+ import re
4
5
  import pyperclip
5
6
  import fnmatch
6
7
 
8
+ import requests
9
+
7
10
  def read_gitignore():
8
11
  default_ignore_patterns = [
9
12
  '.git', 'node_modules', 'venv', '.venv', 'dist', '.idea', '__pycache__',
@@ -11,11 +14,17 @@ def read_gitignore():
11
14
  '.terraform', 'output', 'poetry.lock', 'package-lock.json', '.env',
12
15
  '*.log', '*.bak', '*.swp', '*.swo', '*.tmp', 'tmp', 'temp', 'logs',
13
16
  'build', 'target', '.DS_Store', 'Thumbs.db', '*.class', '*.jar',
14
- '*.war', '*.ear', '*.sqlite', '*.db', '.github', '.gitignore'
17
+ '*.war', '*.ear', '*.sqlite', '*.db', '.github', '.gitignore',
18
+ '*.jpg', '*.jpeg', '*.png', '*.gif', '*.bmp', '*.tiff',
19
+ '*.ico', '*.svg', '*.webp', '*.mp3', '*.mp4', '*.avi',
20
+ '*.mov', '*.wmv', '*.flv', '*.pdf', '*.doc', '*.docx',
21
+ '*.xls', '*.xlsx', '*.ppt', '*.pptx', '*.zip', '*.rar',
22
+ '*.tar', '*.gz', '*.7z', '*.exe', '*.dll', '*.so', '*.dylib'
15
23
  ]
16
24
  gitignore_patterns = default_ignore_patterns.copy()
17
25
 
18
26
  if os.path.exists('.gitignore'):
27
+ print(".gitignore detected.")
19
28
  with open('.gitignore', 'r') as file:
20
29
  for line in file:
21
30
  line = line.strip()
@@ -30,12 +39,26 @@ def is_ignored(path, ignore_patterns):
30
39
  return True
31
40
  return False
32
41
 
42
+ def is_binary(file_path):
43
+ try:
44
+ with open(file_path, 'rb') as file:
45
+ return b'\0' in file.read(1024)
46
+ except IOError:
47
+ return False
48
+
49
+ def get_human_readable_size(size):
50
+ for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
51
+ if size < 1024.0:
52
+ return f"{size:.2f} {unit}"
53
+ size /= 1024.0
54
+
55
+ def is_large_file(file_path, threshold=102400): # 100 KB threshold
56
+ return os.path.getsize(file_path) > threshold
57
+
33
58
  def get_project_structure(ignore_patterns):
34
59
  tree = []
35
60
  for root, dirs, files in os.walk('.'):
36
- # Remove ignored directories
37
61
  dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d), ignore_patterns)]
38
- # Remove ignored files
39
62
  files = [f for f in files if not is_ignored(os.path.join(root, f), ignore_patterns)]
40
63
  level = root.replace('.', '').count(os.sep)
41
64
  indent = ' ' * 4 * level + '|-- '
@@ -77,146 +100,276 @@ def get_language_for_file(file_path):
77
100
  }
78
101
  return language_map.get(extension, '')
79
102
 
80
- def select_files_in_directory(directory, ignore_patterns):
103
+ def get_file_snippet(file_path, max_lines=50, max_bytes=4096):
104
+ snippet = ""
105
+ byte_count = 0
106
+ with open(file_path, 'r') as file:
107
+ for i, line in enumerate(file):
108
+ if i >= max_lines or byte_count >= max_bytes:
109
+ break
110
+ snippet += line
111
+ byte_count += len(line.encode('utf-8'))
112
+ return snippet
113
+
114
+ def select_files_in_directory(directory, ignore_patterns, current_char_count=0):
81
115
  files = [f for f in os.listdir(directory)
82
- if os.path.isfile(os.path.join(directory, f)) and not is_ignored(os.path.join(directory, f), ignore_patterns)]
116
+ if os.path.isfile(os.path.join(directory, f)) and not is_ignored(os.path.join(directory, f), ignore_patterns) and not is_binary(os.path.join(directory, f))]
83
117
 
84
118
  if not files:
85
- return []
119
+ return [], current_char_count
86
120
 
87
121
  print(f"\nDirectory: {directory}")
88
122
  print("Files:")
89
123
  for file in files:
90
- print(f"- {file}")
124
+ file_path = os.path.join(directory, file)
125
+ file_size = os.path.getsize(file_path)
126
+ file_size_readable = get_human_readable_size(file_size)
127
+ file_char_estimate = file_size # Assuming 1 byte ≈ 1 character for text files
128
+ file_token_estimate = file_char_estimate // 4
129
+ print(f"- {file} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens)")
91
130
 
92
131
  while True:
93
- choice = input("\n(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
132
+ print_char_count(current_char_count)
133
+ choice = input("(y)es add all / (n)o ignore all / (s)elect individually / (q)uit? ").lower()
94
134
  if choice == 'y':
135
+ selected_files = []
136
+ for file in files:
137
+ file_path = os.path.join(directory, file)
138
+ if is_large_file(file_path):
139
+ while True:
140
+ snippet_choice = input(f"{file} is large. Use (f)ull content or (s)nippet? ").lower()
141
+ if snippet_choice in ['f', 's']:
142
+ break
143
+ print("Invalid choice. Please enter 'f' or 's'.")
144
+ if snippet_choice == 's':
145
+ selected_files.append((file, True))
146
+ current_char_count += len(get_file_snippet(file_path))
147
+ else:
148
+ selected_files.append((file, False))
149
+ current_char_count += os.path.getsize(file_path)
150
+ else:
151
+ selected_files.append((file, False))
152
+ current_char_count += os.path.getsize(file_path)
95
153
  print(f"Added all files from {directory}")
96
- return files
154
+ return selected_files, current_char_count
97
155
  elif choice == 'n':
98
156
  print(f"Ignored all files from {directory}")
99
- return []
157
+ return [], current_char_count
100
158
  elif choice == 's':
101
159
  selected_files = []
102
160
  for file in files:
161
+ file_path = os.path.join(directory, file)
162
+ file_size = os.path.getsize(file_path)
163
+ file_size_readable = get_human_readable_size(file_size)
164
+ file_char_estimate = file_size
165
+ file_token_estimate = file_char_estimate // 4
103
166
  while True:
104
- file_choice = input(f"{file} (y/n/q)? ").lower()
167
+ if current_char_count > 0:
168
+ print_char_count(current_char_count)
169
+ file_choice = input(f"{file} ({file_size_readable}, ~{file_char_estimate} chars, ~{file_token_estimate} tokens) (y/n/q)? ").lower()
105
170
  if file_choice == 'y':
106
- selected_files.append(file)
171
+ if is_large_file(file_path):
172
+ while True:
173
+ snippet_choice = input(f"{file} is large. Use (f)ull content or (s)nippet? ").lower()
174
+ if snippet_choice in ['f', 's']:
175
+ break
176
+ print("Invalid choice. Please enter 'f' or 's'.")
177
+ if snippet_choice == 's':
178
+ selected_files.append((file, True))
179
+ current_char_count += len(get_file_snippet(file_path))
180
+ else:
181
+ selected_files.append((file, False))
182
+ current_char_count += file_char_estimate
183
+ else:
184
+ selected_files.append((file, False))
185
+ current_char_count += file_char_estimate
107
186
  break
108
187
  elif file_choice == 'n':
109
188
  break
110
189
  elif file_choice == 'q':
111
190
  print(f"Quitting selection for {directory}")
112
- return selected_files
191
+ return selected_files, current_char_count
113
192
  else:
114
193
  print("Invalid choice. Please enter 'y', 'n', or 'q'.")
115
194
  print(f"Added {len(selected_files)} files from {directory}")
116
- return selected_files
195
+ return selected_files, current_char_count
117
196
  elif choice == 'q':
118
197
  print(f"Quitting selection for {directory}")
119
- return []
198
+ return [], current_char_count
120
199
  else:
121
200
  print("Invalid choice. Please try again.")
122
201
 
123
- def process_directory(directory, ignore_patterns):
202
+ def process_directory(directory, ignore_patterns, current_char_count=0):
124
203
  files_to_include = []
125
204
  processed_dirs = set()
126
205
 
127
206
  for root, dirs, files in os.walk(directory):
128
- # Remove ignored directories
129
207
  dirs[:] = [d for d in dirs if not is_ignored(os.path.join(root, d), ignore_patterns)]
130
- # Remove ignored files
131
- files = [f for f in files if not is_ignored(os.path.join(root, f), ignore_patterns)]
208
+ files = [f for f in files if not is_ignored(os.path.join(root, f), ignore_patterns) and not is_binary(os.path.join(root, f))]
132
209
 
133
210
  if root in processed_dirs:
134
211
  continue
135
212
 
136
- selected_files = select_files_in_directory(root, ignore_patterns)
137
- full_paths = [os.path.join(root, f) for f in selected_files]
213
+ selected_files, current_char_count = select_files_in_directory(root, ignore_patterns, current_char_count)
214
+ full_paths = [(os.path.join(root, f), use_snippet) for f, use_snippet in selected_files]
138
215
  files_to_include.extend(full_paths)
139
216
  processed_dirs.add(root)
140
217
 
141
- return files_to_include, processed_dirs
218
+ return files_to_include, processed_dirs, current_char_count
142
219
 
143
- def generate_prompt(files_to_include, ignore_patterns):
144
- prompt = "# Project Overview\n\n"
145
- prompt += "## Summary of Included Files\n\n"
146
- for file in files_to_include:
147
- relative_path = get_relative_path(file)
148
- prompt += f"- {relative_path}\n"
149
- prompt += "\n"
220
+ def fetch_web_content(url):
221
+ try:
222
+ response = requests.get(url)
223
+ response.raise_for_status()
224
+ full_content = response.text
225
+ snippet = full_content[:1000] if len(full_content) > 10000 else full_content
226
+ return full_content, snippet
227
+ except requests.RequestException as e:
228
+ print(f"Error fetching content from {url}: {e}")
229
+ return None, None
230
+
231
+ def read_env_file():
232
+ env_vars = {}
233
+ if os.path.exists('.env'):
234
+ with open('.env', 'r') as env_file:
235
+ for line in env_file:
236
+ line = line.strip()
237
+ if line and not line.startswith('#'):
238
+ key, value = line.split('=', 1)
239
+ env_vars[key.strip()] = value.strip()
240
+ return env_vars
241
+
242
+ def detect_env_variables(content, env_vars):
243
+ detected_vars = []
244
+ for key, value in env_vars.items():
245
+ if value in content:
246
+ detected_vars.append((key, value))
247
+ return detected_vars
248
+
249
+ def handle_env_variables(content, env_vars):
250
+ detected_vars = detect_env_variables(content, env_vars)
251
+ if not detected_vars:
252
+ return content
253
+
254
+ print("Detected environment variables:")
255
+ for key, value in detected_vars:
256
+ print(f"- {key}={value}")
257
+
258
+ for key, value in detected_vars:
259
+ while True:
260
+ choice = input(f"How would you like to handle {key}? (m)ask / (s)kip / (k)eep: ").lower()
261
+ if choice in ['m', 's', 'k']:
262
+ break
263
+ print("Invalid choice. Please enter 'm', 's', or 'k'.")
264
+
265
+ if choice == 'm':
266
+ content = content.replace(value, '*' * len(value))
267
+ elif choice == 's':
268
+ content = content.replace(value, "[REDACTED]")
269
+ # If 'k', we don't modify the content
150
270
 
271
+ return content
272
+
273
+ def generate_prompt(files_to_include, ignore_patterns, web_contents, env_vars):
274
+ prompt = "# Project Overview\n\n"
151
275
  prompt += "## Project Structure\n\n"
152
276
  prompt += "```\n"
153
277
  prompt += get_project_structure(ignore_patterns)
154
278
  prompt += "\n```\n\n"
155
-
156
279
  prompt += "## File Contents\n\n"
157
- for file in files_to_include:
280
+ for file, use_snippet in files_to_include:
158
281
  relative_path = get_relative_path(file)
159
282
  language = get_language_for_file(file)
160
- prompt += f"### {relative_path}\n\n"
161
- prompt += f"```{language}\n"
162
- prompt += read_file_contents(file)
163
- prompt += "\n```\n\n"
164
-
283
+ if use_snippet:
284
+ file_content = get_file_snippet(file)
285
+ prompt += f"### {relative_path} (snippet)\n\n```{language}\n{file_content}\n```\n\n"
286
+ else:
287
+ file_content = read_file_contents(file)
288
+ file_content = handle_env_variables(file_content, env_vars)
289
+ prompt += f"### {relative_path}\n\n```{language}\n{file_content}\n```\n\n"
290
+
291
+ if web_contents:
292
+ prompt += "## Web Content\n\n"
293
+ for url, (full_content, snippet) in web_contents.items():
294
+ content = handle_env_variables(snippet if len(full_content) > 10000 else full_content, env_vars)
295
+ prompt += f"### {url}{' (snippet)' if len(full_content) > 10000 else ''}\n\n```\n{content}\n```\n\n"
296
+
165
297
  prompt += "## Task Instructions\n\n"
166
298
  task_instructions = input("Enter the task instructions: ")
167
299
  prompt += f"{task_instructions}\n\n"
168
-
169
300
  prompt += "## Task Analysis and Planning\n\n"
170
- prompt += (
301
+ analysis_text = (
171
302
  "Before starting, explain the task back to me in your own words. "
172
303
  "Ask for any clarifications if needed. Once you're clear, ask to proceed.\n\n"
173
304
  "Then, outline a plan for the task. Finally, use your plan to complete the task."
174
305
  )
175
-
306
+ prompt += analysis_text
176
307
  return prompt
177
308
 
309
+ def print_char_count(count):
310
+ token_estimate = count // 4
311
+ print(f"\rCurrent prompt size: {count} characters (~ {token_estimate} tokens)", flush=True)
312
+
178
313
  def main():
179
- parser = argparse.ArgumentParser(description="Generate a prompt with project structure and file contents.")
180
- parser.add_argument('inputs', nargs='+', help='Files or directories to include in the prompt')
314
+ parser = argparse.ArgumentParser(description="Generate a prompt with project structure, file contents, and web content.")
315
+ parser.add_argument('inputs', nargs='+', help='Files, directories, or URLs to include in the prompt')
181
316
  args = parser.parse_args()
182
317
 
183
318
  ignore_patterns = read_gitignore()
319
+ env_vars = read_env_file()
184
320
 
185
321
  files_to_include = []
186
322
  processed_dirs = set()
323
+ web_contents = {}
324
+ current_char_count = 0
187
325
 
188
326
  for input_path in args.inputs:
189
- if os.path.isfile(input_path):
190
- if not is_ignored(input_path, ignore_patterns):
191
- files_to_include.append(input_path)
192
- print(f"Added file: {input_path}")
327
+ if input_path.startswith(('http://', 'https://')):
328
+ full_content, snippet = fetch_web_content(input_path)
329
+ if full_content:
330
+ web_contents[input_path] = (full_content, snippet)
331
+ current_char_count += len(snippet if len(full_content) > 10000 else full_content)
332
+ print(f"Added web content from: {input_path}")
333
+ elif os.path.isfile(input_path):
334
+ if not is_ignored(input_path, ignore_patterns) and not is_binary(input_path):
335
+ use_snippet = is_large_file(input_path)
336
+ files_to_include.append((input_path, use_snippet))
337
+ if use_snippet:
338
+ current_char_count += len(get_file_snippet(input_path))
339
+ else:
340
+ current_char_count += os.path.getsize(input_path)
341
+ print(f"Added file: {input_path}{' (snippet)' if use_snippet else ''}")
193
342
  else:
194
343
  print(f"Ignored file: {input_path}")
195
344
  elif os.path.isdir(input_path):
196
- dir_files, dir_processed = process_directory(input_path, ignore_patterns)
345
+ dir_files, dir_processed, current_char_count = process_directory(input_path, ignore_patterns, current_char_count)
197
346
  files_to_include.extend(dir_files)
198
347
  processed_dirs.update(dir_processed)
199
348
  else:
200
- print(f"Warning: {input_path} is not a valid file or directory. Skipping.")
349
+ print(f"Warning: {input_path} is not a valid file, directory, or URL. Skipping.")
201
350
 
202
- if not files_to_include:
203
- print("No files were selected. Exiting.")
351
+ if not files_to_include and not web_contents:
352
+ print("No files or web content were selected. Exiting.")
204
353
  return
205
354
 
206
- print("\nFile selection complete.")
207
- print(f"Summary: Added {len(files_to_include)} files from {len(processed_dirs)} directories.")
355
+ print("\nFile and web content selection complete.")
356
+ print_char_count(current_char_count)
357
+ print(f"Summary: Added {len(files_to_include)} files from {len(processed_dirs)} directories and {len(web_contents)} web sources.")
208
358
 
209
- prompt = generate_prompt(files_to_include, ignore_patterns)
210
- print("\nGenerated prompt:")
359
+ prompt = generate_prompt(files_to_include, ignore_patterns, web_contents, env_vars)
360
+ print("\n\nGenerated prompt:")
211
361
  print(prompt)
212
362
 
213
363
  # Copy the prompt to clipboard
214
364
  try:
215
365
  pyperclip.copy(prompt)
216
- print("\nPrompt has been copied to clipboard.")
366
+ separator = "\n" + "=" * 40 + "\n☕🍝 Kopipasta Complete! 🍝☕\n" + "=" * 40 + "\n"
367
+ print(separator)
368
+ final_char_count = len(prompt)
369
+ final_token_estimate = final_char_count // 4
370
+ print(f"Prompt has been copied to clipboard. Final size: {final_char_count} characters (~ {final_token_estimate} tokens)")
217
371
  except pyperclip.PyperclipException as e:
218
372
  print(f"Failed to copy to clipboard: {e}")
219
373
 
220
374
  if __name__ == "__main__":
221
- main()
222
-
375
+ main()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mikko Korpela
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.1
2
+ Name: kopipasta
3
+ Version: 0.3.0
4
+ Summary: A CLI tool to generate prompts with project structure and file contents
5
+ Home-page: https://github.com/mkorpela/kopipasta
6
+ Author: Mikko Korpela
7
+ Author-email: mikko.korpela@gmail.com
8
+ License: MIT
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: pyperclip==1.9.0
23
+
24
+ # kopipasta
25
+
26
+ A CLI tool to generate prompts with project structure, file contents, and web content, while handling environment variables securely and offering snippets for large files.
27
+
28
+ <img src="kopipasta.jpg" alt="kopipasta" width="300">
29
+
30
+ ## Installation
31
+
32
+ You can install kopipasta using pipx (or pip):
33
+
34
+ ```
35
+ pipx install kopipasta
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ To use kopipasta, run the following command in your terminal:
41
+
42
+ ```
43
+ kopipasta [files_or_directories_or_urls]
44
+ ```
45
+
46
+ Replace `[files_or_directories_or_urls]` with the paths to the files or directories you want to include in the prompt, as well as any web URLs you want to fetch content from.
47
+
48
+ Example:
49
+ ```
50
+ kopipasta src/ config.json https://example.com/api-docs
51
+ ```
52
+
53
+ This will generate a prompt including:
54
+ - The project structure
55
+ - Contents of the specified files and directories (with snippet options for large files)
56
+ - Content fetched from the provided URLs (with snippet options for large content)
57
+ - Handling of environment variables found in a `.env` file (if present)
58
+
59
+ Files and directories typically excluded in version control (based on common .gitignore patterns) are ignored.
60
+
61
+ The generated prompt will be displayed in the console and automatically copied to your clipboard.
62
+
63
+ ## Features
64
+
65
+ - Generates a structured prompt with project overview, file contents, web content, and task instructions
66
+ - Offers snippet options for large files (>100 KB) and web content (>10,000 characters)
67
+ - Fetches and includes content from web URLs
68
+ - Detects and securely handles environment variables from a `.env` file
69
+ - Ignores files and directories based on common .gitignore patterns
70
+ - Allows interactive selection of files to include
71
+ - Automatically copies the generated prompt to the clipboard
72
+
73
+ ## Environment Variable Handling
74
+
75
+ If a `.env` file is present in the current directory, kopipasta will:
76
+ 1. Read and store the environment variables
77
+ 2. Detect these variables in file contents and web content
78
+ 3. Prompt you to choose how to handle each detected variable:
79
+ - (m)ask: Replace the value with asterisks
80
+ - (s)kip: Replace the value with "[REDACTED]"
81
+ - (k)eep: Leave the value as-is
82
+
83
+ This ensures sensitive information is handled securely in the generated prompt.
84
+
85
+ ## Snippet Functionality
86
+
87
+ For large files (>100 KB) and web content (>10,000 characters), kopipasta offers a snippet option:
88
+
89
+ - For files: The first 50 lines or 4 KB (4,096 bytes), whichever comes first
90
+ - For web content: The first 1,000 characters
91
+
92
+ This helps manage the overall prompt size while still providing useful information about the content structure.
93
+
94
+ ## Example output
95
+
96
+ ```bash
97
+ ❯ kopipasta . https://example.com/api-docs
98
+
99
+ Directory: .
100
+ Files:
101
+ - __init__.py
102
+ - main.py (120 KB, ~120000 chars, ~30000 tokens)
103
+ - large_data.csv (5 MB, ~5000000 chars, ~1250000 tokens)
104
+ - .env
105
+
106
+ (y)es add all / (n)o ignore all / (s)elect individually / (q)uit? y
107
+ main.py is large. Use (f)ull content or (s)nippet? s
108
+ large_data.csv is large. Use (f)ull content or (s)nippet? s
109
+ Added all files from .
110
+ Added web content from: https://example.com/api-docs
111
+
112
+ File and web content selection complete.
113
+ Current prompt size: 10500 characters (~ 2625 tokens)
114
+ Summary: Added 3 files from 1 directory and 1 web source.
115
+
116
+ Detected environment variables:
117
+ - API_KEY=12345abcde
118
+
119
+ How would you like to handle API_KEY? (m)ask / (k)eep: m
120
+
121
+ Enter the task instructions: Implement new API endpoint
122
+
123
+ Generated prompt:
124
+ # Project Overview
125
+
126
+ ## Project Structure
127
+
128
+ ```
129
+ |-- ./
130
+ |-- __init__.py
131
+ |-- main.py
132
+ |-- large_data.csv
133
+ |-- .env
134
+ ```
135
+
136
+ ## File Contents
137
+
138
+ ### __init__.py
139
+
140
+ ```python
141
+ # Initialize package
142
+ ```
143
+
144
+ ### main.py (snippet)
145
+
146
+ ```python
147
+ import os
148
+ import pandas as pd
149
+
150
+ API_KEY = os.getenv('API_KEY')
151
+
152
+ def process_data(file_path):
153
+ df = pd.read_csv(file_path)
154
+ # Rest of the function...
155
+
156
+ # More code...
157
+ ```
158
+
159
+ ### large_data.csv (snippet)
160
+
161
+ ```
162
+ id,name,value
163
+ 1,John,100
164
+ 2,Jane,200
165
+ 3,Bob,150
166
+ 4,Alice,300
167
+ # ... (first 50 lines or 4 KB)
168
+ ```
169
+
170
+ ## Web Content
171
+
172
+ ### https://example.com/api-docs (snippet)
173
+
174
+ ```
175
+ API Documentation
176
+ Endpoint: /api/v1/data
177
+ Method: GET
178
+ Authorization: Bearer **********
179
+ ...
180
+ ```
181
+
182
+ ## Task Instructions
183
+
184
+ Implement new API endpoint
185
+
186
+ ## Task Analysis and Planning
187
+
188
+ Before starting, explain the task back to me in your own words. Ask for any clarifications if needed. Once you're clear, ask to proceed.
189
+
190
+ Then, outline a plan for the task. Finally, use your plan to complete the task.
191
+
192
+ Prompt has been copied to clipboard. Final size: 1500 characters (~ 375 tokens)
193
+ ```
194
+
195
+ ## License
196
+
197
+ This project is licensed under the MIT License.
@@ -0,0 +1,8 @@
1
+ kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ kopipasta/main.py,sha256=5-zAPtdF4PU5xbMFJk5x5lmVAAXSIQ0FoKko4znch3k,15829
3
+ kopipasta-0.3.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
4
+ kopipasta-0.3.0.dist-info/METADATA,sha256=rhI1wv4YH5aWoz1rVmLPsEvt1N0Vh_d9EHKr0PhckPM,5399
5
+ kopipasta-0.3.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
6
+ kopipasta-0.3.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
7
+ kopipasta-0.3.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
8
+ kopipasta-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.1.2)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
File without changes
@@ -1,64 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: kopipasta
3
- Version: 0.1.0
4
- Summary: A CLI tool to generate prompts with project structure and file contents
5
- Home-page: https://github.com/mkorpela/kopipasta
6
- Author: Mikko Korpela
7
- Author-email: mikko.korpela@gmail.com
8
- License: MIT
9
- Classifier: Development Status :: 3 - Alpha
10
- Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Classifier: Programming Language :: Python :: 3.12
19
- Requires-Python: >=3.8
20
- Description-Content-Type: text/markdown
21
- License-File: LICENSE
22
- Requires-Dist: pyperclip ==1.9.0
23
-
24
- # kopipasta
25
-
26
- A CLI tool to generate prompts with project structure and file contents.
27
-
28
- ## Installation
29
-
30
- You can install kopipasta using pipx (or pip):
31
-
32
- ```
33
- pipx install kopipasta
34
- ```
35
-
36
- ## Usage
37
-
38
- To use kopipasta, run the following command in your terminal:
39
-
40
- ```
41
- kopipasta [files_or_directories]
42
- ```
43
-
44
- Replace `[files_or_directories]` with the paths to the files or directories you want to include in the prompt.
45
-
46
- Example:
47
- ```
48
- kopipasta . src/ config.json
49
- ```
50
-
51
- This will generate a prompt including the project structure and contents of the specified files and directories, ignoring files and directories typically excluded in version control (based on common .gitignore patterns).
52
-
53
- The generated prompt will be displayed in the console and automatically copied to your clipboard.
54
-
55
- ## Features
56
-
57
- - Generates a structured prompt with project overview, file contents, and task instructions
58
- - Ignores files and directories based on common .gitignore patterns
59
- - Allows interactive selection of files to include
60
- - Automatically copies the generated prompt to the clipboard
61
-
62
- ## License
63
-
64
- This project is licensed under the MIT License.
@@ -1,8 +0,0 @@
1
- kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- kopipasta/main.py,sha256=cTYEe92iZLg9S0MpQGBL01MOcDF6zTjW328QgUPCVHo,7886
3
- kopipasta-0.1.0.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- kopipasta-0.1.0.dist-info/METADATA,sha256=WjpoxaAhAzwT3IXHEM8vTqR6mgUUjhlyQhC-m9YQiCM,1987
5
- kopipasta-0.1.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
6
- kopipasta-0.1.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
7
- kopipasta-0.1.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
8
- kopipasta-0.1.0.dist-info/RECORD,,