kopipasta 0.22.0__py3-none-any.whl → 0.24.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 +29 -68
- {kopipasta-0.22.0.dist-info → kopipasta-0.24.0.dist-info}/METADATA +6 -5
- kopipasta-0.24.0.dist-info/RECORD +8 -0
- {kopipasta-0.22.0.dist-info → kopipasta-0.24.0.dist-info}/WHEEL +1 -1
- kopipasta-0.22.0.dist-info/RECORD +0 -8
- {kopipasta-0.22.0.dist-info → kopipasta-0.24.0.dist-info}/LICENSE +0 -0
- {kopipasta-0.22.0.dist-info → kopipasta-0.24.0.dist-info}/entry_points.txt +0 -0
- {kopipasta-0.22.0.dist-info → kopipasta-0.24.0.dist-info}/top_level.txt +0 -0
kopipasta/main.py
CHANGED
|
@@ -22,6 +22,7 @@ from pydantic import BaseModel, Field
|
|
|
22
22
|
import traceback
|
|
23
23
|
from google import genai
|
|
24
24
|
from google.genai.types import GenerateContentConfig
|
|
25
|
+
from prompt_toolkit import prompt # Added for multiline input
|
|
25
26
|
|
|
26
27
|
FileTuple = Tuple[str, bool, Optional[List[str]], str]
|
|
27
28
|
|
|
@@ -682,65 +683,6 @@ def fetch_web_content(url: str) -> Tuple[Optional[FileTuple], Optional[str], Opt
|
|
|
682
683
|
print(f"Error fetching content from {url}: {e}")
|
|
683
684
|
return None, None, None
|
|
684
685
|
|
|
685
|
-
def read_file_content(file_path):
|
|
686
|
-
_, ext = os.path.splitext(file_path)
|
|
687
|
-
if ext.lower() == '.json':
|
|
688
|
-
with open(file_path, 'r') as f:
|
|
689
|
-
return json.load(f), 'json'
|
|
690
|
-
elif ext.lower() == '.csv':
|
|
691
|
-
with open(file_path, 'r') as f:
|
|
692
|
-
return f.read(), 'csv'
|
|
693
|
-
else:
|
|
694
|
-
with open(file_path, 'r') as f:
|
|
695
|
-
return f.read(), 'text'
|
|
696
|
-
|
|
697
|
-
def get_content_snippet(content, content_type, max_lines=50, max_chars=4096):
|
|
698
|
-
if content_type == 'json':
|
|
699
|
-
return json.dumps(content, indent=2)[:max_chars]
|
|
700
|
-
elif content_type == 'csv':
|
|
701
|
-
csv_content = content if isinstance(content, str) else content.getvalue()
|
|
702
|
-
csv_reader = csv.reader(io.StringIO(csv_content))
|
|
703
|
-
rows = list(csv_reader)[:max_lines]
|
|
704
|
-
output = io.StringIO()
|
|
705
|
-
csv.writer(output).writerows(rows)
|
|
706
|
-
return output.getvalue()[:max_chars]
|
|
707
|
-
else:
|
|
708
|
-
return '\n'.join(content.split('\n')[:max_lines])[:max_chars]
|
|
709
|
-
|
|
710
|
-
def handle_content(content, content_type, file_or_url):
|
|
711
|
-
is_large = len(json.dumps(content)) > 102400 if content_type == 'json' else len(content) > 102400
|
|
712
|
-
|
|
713
|
-
if is_large:
|
|
714
|
-
while True:
|
|
715
|
-
choice = input(f"{file_or_url} is large. View (f)ull content, (s)nippet, or (p)review? ").lower()
|
|
716
|
-
if choice in ['f', 's', 'p']:
|
|
717
|
-
break
|
|
718
|
-
print("Invalid choice. Please enter 'f', 's', or 'p'.")
|
|
719
|
-
|
|
720
|
-
if choice == 'f':
|
|
721
|
-
return content, False
|
|
722
|
-
elif choice == 's':
|
|
723
|
-
return get_content_snippet(content, content_type), True
|
|
724
|
-
else: # preview
|
|
725
|
-
preview = get_content_preview(content, content_type)
|
|
726
|
-
print(f"\nPreview of {file_or_url}:\n{preview}\n")
|
|
727
|
-
return handle_content(content, content_type, file_or_url)
|
|
728
|
-
else:
|
|
729
|
-
return content, False
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
def get_content_preview(content, content_type):
|
|
733
|
-
if content_type == 'json':
|
|
734
|
-
return json.dumps(content, indent=2)[:1000] + "\n..."
|
|
735
|
-
elif content_type == 'csv':
|
|
736
|
-
csv_content = content if isinstance(content, str) else content.getvalue()
|
|
737
|
-
csv_reader = csv.reader(io.StringIO(csv_content))
|
|
738
|
-
rows = list(csv_reader)[:10]
|
|
739
|
-
output = io.StringIO()
|
|
740
|
-
csv.writer(output).writerows(rows)
|
|
741
|
-
return output.getvalue() + "\n..."
|
|
742
|
-
else:
|
|
743
|
-
return '\n'.join(content.split('\n')[:20]) + "\n..."
|
|
744
686
|
|
|
745
687
|
def read_env_file():
|
|
746
688
|
env_vars = {}
|
|
@@ -920,9 +862,14 @@ def start_chat_session(initial_prompt: str):
|
|
|
920
862
|
print("\n" + "-"*20)
|
|
921
863
|
|
|
922
864
|
while True:
|
|
865
|
+
is_patch_request = False
|
|
923
866
|
try:
|
|
924
|
-
|
|
925
|
-
|
|
867
|
+
# Print the header on a separate line
|
|
868
|
+
print("👤 You (Submit with Esc+Enter):")
|
|
869
|
+
# Get input using prompt_toolkit with a minimal indicator
|
|
870
|
+
user_input = prompt(">> ", multiline=True)
|
|
871
|
+
# prompt_toolkit raises EOFError on Ctrl+D, so this handler remains correct.
|
|
872
|
+
except EOFError:
|
|
926
873
|
print("\nExiting...")
|
|
927
874
|
break
|
|
928
875
|
except KeyboardInterrupt: # Handle Ctrl+C
|
|
@@ -931,19 +878,33 @@ def start_chat_session(initial_prompt: str):
|
|
|
931
878
|
|
|
932
879
|
if user_input.lower() == '/q':
|
|
933
880
|
break
|
|
934
|
-
elif user_input.
|
|
935
|
-
|
|
936
|
-
#
|
|
881
|
+
elif user_input.endswith('/patch'):
|
|
882
|
+
is_patch_request = True
|
|
883
|
+
# Extract message before /patch
|
|
884
|
+
user_message = user_input[:-len('/patch')].strip()
|
|
885
|
+
print(f"\n🛠️ Requesting patches... (Context: '{user_message}' if provided)")
|
|
886
|
+
elif not user_input:
|
|
887
|
+
continue # Ignore empty input
|
|
888
|
+
else:
|
|
889
|
+
user_message = user_input # Regular message
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
# --- Handle Patch Request ---
|
|
893
|
+
if is_patch_request:
|
|
894
|
+
print("🤖 Gemini: Thinking... (generating code changes)")
|
|
895
|
+
# Include user message part if it exists
|
|
896
|
+
patch_context = f"Based on our conversation and specifically: \"{user_message}\"\n\n" if user_message else "Based on our conversation,\n\n"
|
|
897
|
+
|
|
937
898
|
patch_request_prompt = (
|
|
938
|
-
|
|
939
|
-
"to fulfill
|
|
899
|
+
patch_context +
|
|
900
|
+
"Generate the necessary code changes to fulfill the request. Provide the changes as a JSON list, where each item "
|
|
940
901
|
"is an object with the following keys:\n"
|
|
941
902
|
"- 'reasoning': Explain why this specific change is needed.\n"
|
|
942
903
|
"- 'file_path': The relative path to the file to modify.\n"
|
|
943
904
|
"- 'original_text': The exact, unique block of text to replace.\n"
|
|
944
|
-
"- 'new_text': The text to replace original_text with.\n"
|
|
905
|
+
"- 'new_text': The text to replace original_text with. Do not include any temporary comments like '// CHANGE BEGINS' or '/* PATCH START */'.\n"
|
|
945
906
|
"Ensure 'original_text' is unique within the specified 'file_path'. "
|
|
946
|
-
"
|
|
907
|
+
"Respond ONLY with the JSON object conforming to this structure: { \"patches\": [ { patch_item_1 }, { patch_item_2 }, ... ] }"
|
|
947
908
|
)
|
|
948
909
|
|
|
949
910
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: kopipasta
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24.0
|
|
4
4
|
Summary: A CLI tool to generate prompts with project structure and file contents
|
|
5
5
|
Home-page: https://github.com/mkorpela/kopipasta
|
|
6
6
|
Author: Mikko Korpela
|
|
@@ -19,10 +19,11 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
19
19
|
Requires-Python: >=3.8
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
|
-
Requires-Dist: pyperclip
|
|
23
|
-
Requires-Dist: requests
|
|
24
|
-
Requires-Dist: Pygments
|
|
25
|
-
Requires-Dist: google-genai
|
|
22
|
+
Requires-Dist: pyperclip==1.9.0
|
|
23
|
+
Requires-Dist: requests==2.32.3
|
|
24
|
+
Requires-Dist: Pygments==2.18.0
|
|
25
|
+
Requires-Dist: google-genai==1.8.0
|
|
26
|
+
Requires-Dist: prompt-toolkit==3.0.50
|
|
26
27
|
|
|
27
28
|
# kopipasta
|
|
28
29
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
kopipasta/main.py,sha256=iJFQFiN-3udjud2byspXl8RZwZnTg6FPw_HW1exjPbg,53035
|
|
3
|
+
kopipasta-0.24.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
|
|
4
|
+
kopipasta-0.24.0.dist-info/METADATA,sha256=EiMoa_TkI-4SMaMiyiUqroXAvwz1JFM8_ECINOw4fZM,8610
|
|
5
|
+
kopipasta-0.24.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
6
|
+
kopipasta-0.24.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
|
|
7
|
+
kopipasta-0.24.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
|
|
8
|
+
kopipasta-0.24.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
kopipasta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
kopipasta/main.py,sha256=RSoKARL9jnyikiXB1DvZz_lpWLhhQUovQuT6YUj7RGY,54292
|
|
3
|
-
kopipasta-0.22.0.dist-info/LICENSE,sha256=xw4C9TAU7LFu4r_MwSbky90uzkzNtRwAo3c51IWR8lk,1091
|
|
4
|
-
kopipasta-0.22.0.dist-info/METADATA,sha256=V2XrhJR8QIuCIa4iCDFuo5gyW1iJgihWK_Y6H3mVjMI,8576
|
|
5
|
-
kopipasta-0.22.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
6
|
-
kopipasta-0.22.0.dist-info/entry_points.txt,sha256=but54qDNz1-F8fVvGstq_QID5tHjczP7bO7rWLFkc6Y,50
|
|
7
|
-
kopipasta-0.22.0.dist-info/top_level.txt,sha256=iXohixMuCdw8UjGDUp0ouICLYBDrx207sgZIJ9lxn0o,10
|
|
8
|
-
kopipasta-0.22.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|