nc1709 1.15.4__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.
Files changed (86) hide show
  1. nc1709/__init__.py +13 -0
  2. nc1709/agent/__init__.py +36 -0
  3. nc1709/agent/core.py +505 -0
  4. nc1709/agent/mcp_bridge.py +245 -0
  5. nc1709/agent/permissions.py +298 -0
  6. nc1709/agent/tools/__init__.py +21 -0
  7. nc1709/agent/tools/base.py +440 -0
  8. nc1709/agent/tools/bash_tool.py +367 -0
  9. nc1709/agent/tools/file_tools.py +454 -0
  10. nc1709/agent/tools/notebook_tools.py +516 -0
  11. nc1709/agent/tools/search_tools.py +322 -0
  12. nc1709/agent/tools/task_tool.py +284 -0
  13. nc1709/agent/tools/web_tools.py +555 -0
  14. nc1709/agents/__init__.py +17 -0
  15. nc1709/agents/auto_fix.py +506 -0
  16. nc1709/agents/test_generator.py +507 -0
  17. nc1709/checkpoints.py +372 -0
  18. nc1709/cli.py +3380 -0
  19. nc1709/cli_ui.py +1080 -0
  20. nc1709/cognitive/__init__.py +149 -0
  21. nc1709/cognitive/anticipation.py +594 -0
  22. nc1709/cognitive/context_engine.py +1046 -0
  23. nc1709/cognitive/council.py +824 -0
  24. nc1709/cognitive/learning.py +761 -0
  25. nc1709/cognitive/router.py +583 -0
  26. nc1709/cognitive/system.py +519 -0
  27. nc1709/config.py +155 -0
  28. nc1709/custom_commands.py +300 -0
  29. nc1709/executor.py +333 -0
  30. nc1709/file_controller.py +354 -0
  31. nc1709/git_integration.py +308 -0
  32. nc1709/github_integration.py +477 -0
  33. nc1709/image_input.py +446 -0
  34. nc1709/linting.py +519 -0
  35. nc1709/llm_adapter.py +667 -0
  36. nc1709/logger.py +192 -0
  37. nc1709/mcp/__init__.py +18 -0
  38. nc1709/mcp/client.py +370 -0
  39. nc1709/mcp/manager.py +407 -0
  40. nc1709/mcp/protocol.py +210 -0
  41. nc1709/mcp/server.py +473 -0
  42. nc1709/memory/__init__.py +20 -0
  43. nc1709/memory/embeddings.py +325 -0
  44. nc1709/memory/indexer.py +474 -0
  45. nc1709/memory/sessions.py +432 -0
  46. nc1709/memory/vector_store.py +451 -0
  47. nc1709/models/__init__.py +86 -0
  48. nc1709/models/detector.py +377 -0
  49. nc1709/models/formats.py +315 -0
  50. nc1709/models/manager.py +438 -0
  51. nc1709/models/registry.py +497 -0
  52. nc1709/performance/__init__.py +343 -0
  53. nc1709/performance/cache.py +705 -0
  54. nc1709/performance/pipeline.py +611 -0
  55. nc1709/performance/tiering.py +543 -0
  56. nc1709/plan_mode.py +362 -0
  57. nc1709/plugins/__init__.py +17 -0
  58. nc1709/plugins/agents/__init__.py +18 -0
  59. nc1709/plugins/agents/django_agent.py +912 -0
  60. nc1709/plugins/agents/docker_agent.py +623 -0
  61. nc1709/plugins/agents/fastapi_agent.py +887 -0
  62. nc1709/plugins/agents/git_agent.py +731 -0
  63. nc1709/plugins/agents/nextjs_agent.py +867 -0
  64. nc1709/plugins/base.py +359 -0
  65. nc1709/plugins/manager.py +411 -0
  66. nc1709/plugins/registry.py +337 -0
  67. nc1709/progress.py +443 -0
  68. nc1709/prompts/__init__.py +22 -0
  69. nc1709/prompts/agent_system.py +180 -0
  70. nc1709/prompts/task_prompts.py +340 -0
  71. nc1709/prompts/unified_prompt.py +133 -0
  72. nc1709/reasoning_engine.py +541 -0
  73. nc1709/remote_client.py +266 -0
  74. nc1709/shell_completions.py +349 -0
  75. nc1709/slash_commands.py +649 -0
  76. nc1709/task_classifier.py +408 -0
  77. nc1709/version_check.py +177 -0
  78. nc1709/web/__init__.py +8 -0
  79. nc1709/web/server.py +950 -0
  80. nc1709/web/templates/index.html +1127 -0
  81. nc1709-1.15.4.dist-info/METADATA +858 -0
  82. nc1709-1.15.4.dist-info/RECORD +86 -0
  83. nc1709-1.15.4.dist-info/WHEEL +5 -0
  84. nc1709-1.15.4.dist-info/entry_points.txt +2 -0
  85. nc1709-1.15.4.dist-info/licenses/LICENSE +9 -0
  86. nc1709-1.15.4.dist-info/top_level.txt +1 -0
nc1709/image_input.py ADDED
@@ -0,0 +1,446 @@
1
+ """
2
+ Image Input Support for NC1709
3
+
4
+ Provides multi-modal capabilities for image/screenshot input:
5
+ - Read and encode images for LLM API calls
6
+ - Support for PNG, JPG, GIF, WebP formats
7
+ - Screenshot capture (macOS support)
8
+ - Clipboard image paste support
9
+ """
10
+
11
+ import os
12
+ import base64
13
+ import mimetypes
14
+ from pathlib import Path
15
+ from typing import Optional, List, Dict, Any, Tuple
16
+ from dataclasses import dataclass
17
+ import subprocess
18
+ import tempfile
19
+
20
+
21
+ # Supported image formats
22
+ SUPPORTED_IMAGE_EXTENSIONS = {'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'}
23
+ SUPPORTED_MIME_TYPES = {
24
+ '.png': 'image/png',
25
+ '.jpg': 'image/jpeg',
26
+ '.jpeg': 'image/jpeg',
27
+ '.gif': 'image/gif',
28
+ '.webp': 'image/webp',
29
+ '.bmp': 'image/bmp',
30
+ }
31
+
32
+
33
+ @dataclass
34
+ class ImageData:
35
+ """Represents an image for multi-modal input"""
36
+ path: str
37
+ base64_data: str
38
+ mime_type: str
39
+ width: Optional[int] = None
40
+ height: Optional[int] = None
41
+ size_bytes: int = 0
42
+
43
+
44
+ def is_image_file(file_path: str) -> bool:
45
+ """Check if a file is a supported image format"""
46
+ path = Path(file_path)
47
+ return path.suffix.lower() in SUPPORTED_IMAGE_EXTENSIONS
48
+
49
+
50
+ def get_image_mime_type(file_path: str) -> str:
51
+ """Get MIME type for an image file"""
52
+ path = Path(file_path)
53
+ ext = path.suffix.lower()
54
+ return SUPPORTED_MIME_TYPES.get(ext, 'image/png')
55
+
56
+
57
+ def encode_image_to_base64(file_path: str) -> Optional[str]:
58
+ """
59
+ Read an image file and encode it to base64.
60
+
61
+ Args:
62
+ file_path: Path to the image file
63
+
64
+ Returns:
65
+ Base64 encoded string, or None if failed
66
+ """
67
+ path = Path(file_path).expanduser()
68
+
69
+ if not path.exists():
70
+ return None
71
+
72
+ if not is_image_file(str(path)):
73
+ return None
74
+
75
+ try:
76
+ with open(path, 'rb') as f:
77
+ image_data = f.read()
78
+ return base64.b64encode(image_data).decode('utf-8')
79
+ except Exception:
80
+ return None
81
+
82
+
83
+ def load_image(file_path: str) -> Optional[ImageData]:
84
+ """
85
+ Load an image file and prepare it for multi-modal input.
86
+
87
+ Args:
88
+ file_path: Path to the image file
89
+
90
+ Returns:
91
+ ImageData object, or None if failed
92
+ """
93
+ path = Path(file_path).expanduser()
94
+
95
+ if not path.exists():
96
+ return None
97
+
98
+ if not is_image_file(str(path)):
99
+ return None
100
+
101
+ base64_data = encode_image_to_base64(str(path))
102
+ if not base64_data:
103
+ return None
104
+
105
+ # Get file size
106
+ size_bytes = path.stat().st_size
107
+
108
+ # Try to get image dimensions (optional, requires PIL)
109
+ width, height = None, None
110
+ try:
111
+ from PIL import Image
112
+ with Image.open(path) as img:
113
+ width, height = img.size
114
+ except ImportError:
115
+ pass
116
+ except Exception:
117
+ pass
118
+
119
+ return ImageData(
120
+ path=str(path.absolute()),
121
+ base64_data=base64_data,
122
+ mime_type=get_image_mime_type(str(path)),
123
+ width=width,
124
+ height=height,
125
+ size_bytes=size_bytes
126
+ )
127
+
128
+
129
+ def capture_screenshot(output_path: Optional[str] = None) -> Optional[str]:
130
+ """
131
+ Capture a screenshot (macOS only for now).
132
+
133
+ Args:
134
+ output_path: Optional path to save screenshot (uses temp file if not provided)
135
+
136
+ Returns:
137
+ Path to the saved screenshot, or None if failed
138
+ """
139
+ import platform
140
+
141
+ if platform.system() != 'Darwin':
142
+ return None
143
+
144
+ if output_path is None:
145
+ # Create a temporary file
146
+ fd, output_path = tempfile.mkstemp(suffix='.png', prefix='nc1709_screenshot_')
147
+ os.close(fd)
148
+
149
+ try:
150
+ # Use macOS screencapture command
151
+ # -i for interactive selection, -x for no sound
152
+ result = subprocess.run(
153
+ ['screencapture', '-i', '-x', output_path],
154
+ capture_output=True,
155
+ timeout=60
156
+ )
157
+
158
+ if result.returncode == 0 and Path(output_path).exists():
159
+ return output_path
160
+ else:
161
+ # User cancelled or error
162
+ if Path(output_path).exists():
163
+ os.remove(output_path)
164
+ return None
165
+
166
+ except subprocess.TimeoutExpired:
167
+ if Path(output_path).exists():
168
+ os.remove(output_path)
169
+ return None
170
+ except Exception:
171
+ return None
172
+
173
+
174
+ def get_clipboard_image() -> Optional[str]:
175
+ """
176
+ Get image from clipboard (macOS only for now).
177
+
178
+ Returns:
179
+ Path to temporary file containing the image, or None if no image in clipboard
180
+ """
181
+ import platform
182
+
183
+ if platform.system() != 'Darwin':
184
+ return None
185
+
186
+ try:
187
+ # Create temp file for clipboard image
188
+ fd, temp_path = tempfile.mkstemp(suffix='.png', prefix='nc1709_clipboard_')
189
+ os.close(fd)
190
+
191
+ # Use pngpaste or osascript to get clipboard image
192
+ # First try pngpaste if available
193
+ result = subprocess.run(
194
+ ['which', 'pngpaste'],
195
+ capture_output=True
196
+ )
197
+
198
+ if result.returncode == 0:
199
+ # pngpaste is available
200
+ result = subprocess.run(
201
+ ['pngpaste', temp_path],
202
+ capture_output=True,
203
+ timeout=10
204
+ )
205
+ if result.returncode == 0 and Path(temp_path).exists():
206
+ return temp_path
207
+ else:
208
+ # Fallback to osascript
209
+ script = '''
210
+ tell application "System Events"
211
+ set clipboardData to the clipboard as «class PNGf»
212
+ end tell
213
+ '''
214
+ # This is more complex, skip for now
215
+ pass
216
+
217
+ # Cleanup if failed
218
+ if Path(temp_path).exists():
219
+ os.remove(temp_path)
220
+ return None
221
+
222
+ except Exception:
223
+ return None
224
+
225
+
226
+ def format_image_for_api(image: ImageData, api_type: str = "anthropic") -> Dict[str, Any]:
227
+ """
228
+ Format image data for API request.
229
+
230
+ Args:
231
+ image: ImageData object
232
+ api_type: API type ("anthropic", "openai")
233
+
234
+ Returns:
235
+ Dict formatted for the API
236
+ """
237
+ if api_type == "anthropic":
238
+ return {
239
+ "type": "image",
240
+ "source": {
241
+ "type": "base64",
242
+ "media_type": image.mime_type,
243
+ "data": image.base64_data
244
+ }
245
+ }
246
+ elif api_type == "openai":
247
+ return {
248
+ "type": "image_url",
249
+ "image_url": {
250
+ "url": f"data:{image.mime_type};base64,{image.base64_data}"
251
+ }
252
+ }
253
+ else:
254
+ raise ValueError(f"Unknown API type: {api_type}")
255
+
256
+
257
+ def extract_image_references(text: str) -> List[str]:
258
+ """
259
+ Extract image file references from text.
260
+
261
+ Supports formats like:
262
+ - @image:path/to/image.png
263
+ - [image: path/to/image.jpg]
264
+ - {{image: /absolute/path.png}}
265
+
266
+ Args:
267
+ text: Input text
268
+
269
+ Returns:
270
+ List of image paths found
271
+ """
272
+ import re
273
+
274
+ patterns = [
275
+ r'@image:([^\s]+)',
276
+ r'\[image:\s*([^\]]+)\]',
277
+ r'\{\{image:\s*([^}]+)\}\}',
278
+ ]
279
+
280
+ image_paths = []
281
+ for pattern in patterns:
282
+ matches = re.findall(pattern, text, re.IGNORECASE)
283
+ image_paths.extend([m.strip() for m in matches])
284
+
285
+ return image_paths
286
+
287
+
288
+ def process_prompt_with_images(
289
+ prompt: str,
290
+ additional_images: Optional[List[str]] = None
291
+ ) -> Tuple[str, List[ImageData]]:
292
+ """
293
+ Process a prompt and extract/load any referenced images.
294
+
295
+ Args:
296
+ prompt: User prompt text
297
+ additional_images: Optional list of additional image paths
298
+
299
+ Returns:
300
+ Tuple of (cleaned prompt, list of ImageData)
301
+ """
302
+ import re
303
+
304
+ images = []
305
+ cleaned_prompt = prompt
306
+
307
+ # Extract image references from prompt
308
+ image_paths = extract_image_references(prompt)
309
+
310
+ # Add additional images
311
+ if additional_images:
312
+ image_paths.extend(additional_images)
313
+
314
+ # Load each image
315
+ for path in image_paths:
316
+ image = load_image(path)
317
+ if image:
318
+ images.append(image)
319
+ else:
320
+ # Keep the reference but note it failed
321
+ print(f"Warning: Could not load image: {path}")
322
+
323
+ # Clean the prompt by removing image references
324
+ patterns = [
325
+ r'@image:[^\s]+',
326
+ r'\[image:\s*[^\]]+\]',
327
+ r'\{\{image:\s*[^}]+\}\}',
328
+ ]
329
+ for pattern in patterns:
330
+ cleaned_prompt = re.sub(pattern, '', cleaned_prompt, flags=re.IGNORECASE)
331
+
332
+ # Clean up extra whitespace
333
+ cleaned_prompt = ' '.join(cleaned_prompt.split())
334
+
335
+ return cleaned_prompt, images
336
+
337
+
338
+ def get_image_info(file_path: str) -> Optional[Dict[str, Any]]:
339
+ """
340
+ Get information about an image file.
341
+
342
+ Args:
343
+ file_path: Path to the image
344
+
345
+ Returns:
346
+ Dict with image info, or None if failed
347
+ """
348
+ path = Path(file_path).expanduser()
349
+
350
+ if not path.exists() or not is_image_file(str(path)):
351
+ return None
352
+
353
+ info = {
354
+ "path": str(path.absolute()),
355
+ "name": path.name,
356
+ "format": path.suffix.lower()[1:],
357
+ "size_bytes": path.stat().st_size,
358
+ "size_human": format_file_size(path.stat().st_size),
359
+ }
360
+
361
+ # Try to get dimensions
362
+ try:
363
+ from PIL import Image
364
+ with Image.open(path) as img:
365
+ info["width"] = img.width
366
+ info["height"] = img.height
367
+ info["mode"] = img.mode
368
+ except ImportError:
369
+ pass
370
+ except Exception:
371
+ pass
372
+
373
+ return info
374
+
375
+
376
+ def format_file_size(size_bytes: int) -> str:
377
+ """Format file size in human-readable format"""
378
+ for unit in ['B', 'KB', 'MB', 'GB']:
379
+ if size_bytes < 1024:
380
+ return f"{size_bytes:.1f} {unit}"
381
+ size_bytes /= 1024
382
+ return f"{size_bytes:.1f} TB"
383
+
384
+
385
+ class ImageInputHandler:
386
+ """
387
+ Handler for image input in the CLI.
388
+
389
+ Provides methods for:
390
+ - Loading images from paths
391
+ - Capturing screenshots
392
+ - Managing image context for prompts
393
+ """
394
+
395
+ def __init__(self):
396
+ self._pending_images: List[ImageData] = []
397
+
398
+ def add_image(self, path: str) -> bool:
399
+ """Add an image to the pending list"""
400
+ image = load_image(path)
401
+ if image:
402
+ self._pending_images.append(image)
403
+ return True
404
+ return False
405
+
406
+ def add_screenshot(self) -> bool:
407
+ """Capture and add a screenshot"""
408
+ path = capture_screenshot()
409
+ if path:
410
+ return self.add_image(path)
411
+ return False
412
+
413
+ def add_clipboard(self) -> bool:
414
+ """Add image from clipboard"""
415
+ path = get_clipboard_image()
416
+ if path:
417
+ return self.add_image(path)
418
+ return False
419
+
420
+ def get_pending_images(self) -> List[ImageData]:
421
+ """Get all pending images"""
422
+ return self._pending_images.copy()
423
+
424
+ def clear_pending(self) -> None:
425
+ """Clear pending images"""
426
+ self._pending_images = []
427
+
428
+ def has_pending_images(self) -> bool:
429
+ """Check if there are pending images"""
430
+ return len(self._pending_images) > 0
431
+
432
+ def format_for_api(self, api_type: str = "anthropic") -> List[Dict[str, Any]]:
433
+ """Format pending images for API request"""
434
+ return [format_image_for_api(img, api_type) for img in self._pending_images]
435
+
436
+
437
+ # Global image handler
438
+ _image_handler: Optional[ImageInputHandler] = None
439
+
440
+
441
+ def get_image_handler() -> ImageInputHandler:
442
+ """Get or create the global image handler"""
443
+ global _image_handler
444
+ if _image_handler is None:
445
+ _image_handler = ImageInputHandler()
446
+ return _image_handler