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/logger.py ADDED
@@ -0,0 +1,192 @@
1
+ """
2
+ Structured logging for NC1709 CLI
3
+ Provides consistent, configurable logging across all modules
4
+ """
5
+ import logging
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ from datetime import datetime
10
+
11
+
12
+ class NC1709Logger:
13
+ """Custom logger with colored console output and file logging"""
14
+
15
+ # ANSI color codes
16
+ COLORS = {
17
+ "DEBUG": "\033[36m", # Cyan
18
+ "INFO": "\033[32m", # Green
19
+ "WARNING": "\033[33m", # Yellow
20
+ "ERROR": "\033[31m", # Red
21
+ "CRITICAL": "\033[35m", # Magenta
22
+ "RESET": "\033[0m"
23
+ }
24
+
25
+ # Emoji prefixes for different log levels
26
+ EMOJIS = {
27
+ "DEBUG": "🔍",
28
+ "INFO": "✅",
29
+ "WARNING": "⚠️",
30
+ "ERROR": "❌",
31
+ "CRITICAL": "🚨"
32
+ }
33
+
34
+ def __init__(
35
+ self,
36
+ name: str = "nc1709",
37
+ level: int = logging.INFO,
38
+ log_file: Optional[str] = None,
39
+ use_colors: bool = True,
40
+ use_emojis: bool = True
41
+ ):
42
+ """Initialize the logger
43
+
44
+ Args:
45
+ name: Logger name
46
+ level: Logging level
47
+ log_file: Optional path to log file
48
+ use_colors: Whether to use colored output
49
+ use_emojis: Whether to use emoji prefixes
50
+ """
51
+ self.logger = logging.getLogger(name)
52
+ self.logger.setLevel(level)
53
+ self.logger.handlers = [] # Clear any existing handlers
54
+
55
+ self.use_colors = use_colors
56
+ self.use_emojis = use_emojis
57
+
58
+ # Console handler
59
+ console_handler = logging.StreamHandler(sys.stderr)
60
+ console_handler.setLevel(level)
61
+ console_handler.setFormatter(self._create_formatter(colored=use_colors))
62
+ self.logger.addHandler(console_handler)
63
+
64
+ # File handler (if specified)
65
+ if log_file:
66
+ log_path = Path(log_file).expanduser()
67
+ log_path.parent.mkdir(parents=True, exist_ok=True)
68
+ file_handler = logging.FileHandler(log_path)
69
+ file_handler.setLevel(logging.DEBUG) # Log everything to file
70
+ file_handler.setFormatter(self._create_formatter(colored=False))
71
+ self.logger.addHandler(file_handler)
72
+
73
+ def _create_formatter(self, colored: bool = False) -> logging.Formatter:
74
+ """Create a log formatter
75
+
76
+ Args:
77
+ colored: Whether to include color codes
78
+
79
+ Returns:
80
+ Logging formatter
81
+ """
82
+ if colored:
83
+ return ColoredFormatter(self.COLORS, self.EMOJIS, self.use_emojis)
84
+ else:
85
+ return logging.Formatter(
86
+ "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
87
+ datefmt="%Y-%m-%d %H:%M:%S"
88
+ )
89
+
90
+ def debug(self, msg: str, *args, **kwargs):
91
+ """Log debug message"""
92
+ self.logger.debug(msg, *args, **kwargs)
93
+
94
+ def info(self, msg: str, *args, **kwargs):
95
+ """Log info message"""
96
+ self.logger.info(msg, *args, **kwargs)
97
+
98
+ def warning(self, msg: str, *args, **kwargs):
99
+ """Log warning message"""
100
+ self.logger.warning(msg, *args, **kwargs)
101
+
102
+ def error(self, msg: str, *args, **kwargs):
103
+ """Log error message"""
104
+ self.logger.error(msg, *args, **kwargs)
105
+
106
+ def critical(self, msg: str, *args, **kwargs):
107
+ """Log critical message"""
108
+ self.logger.critical(msg, *args, **kwargs)
109
+
110
+ def set_level(self, level: int):
111
+ """Set logging level"""
112
+ self.logger.setLevel(level)
113
+ for handler in self.logger.handlers:
114
+ if isinstance(handler, logging.StreamHandler):
115
+ handler.setLevel(level)
116
+
117
+
118
+ class ColoredFormatter(logging.Formatter):
119
+ """Custom formatter with colors and emojis"""
120
+
121
+ def __init__(self, colors: dict, emojis: dict, use_emojis: bool = True):
122
+ super().__init__()
123
+ self.colors = colors
124
+ self.emojis = emojis
125
+ self.use_emojis = use_emojis
126
+
127
+ def format(self, record: logging.LogRecord) -> str:
128
+ level = record.levelname
129
+ color = self.colors.get(level, "")
130
+ reset = self.colors.get("RESET", "")
131
+ emoji = self.emojis.get(level, "") if self.use_emojis else ""
132
+
133
+ # Format: emoji [LEVEL] message
134
+ if emoji:
135
+ formatted = f"{emoji} {color}[{level}]{reset} {record.getMessage()}"
136
+ else:
137
+ formatted = f"{color}[{level}]{reset} {record.getMessage()}"
138
+
139
+ return formatted
140
+
141
+
142
+ # Global logger instance
143
+ _logger: Optional[NC1709Logger] = None
144
+
145
+
146
+ def get_logger(
147
+ name: str = "nc1709",
148
+ level: Optional[int] = None,
149
+ log_file: Optional[str] = None
150
+ ) -> NC1709Logger:
151
+ """Get or create the global logger instance
152
+
153
+ Args:
154
+ name: Logger name
155
+ level: Logging level (default: INFO)
156
+ log_file: Optional log file path
157
+
158
+ Returns:
159
+ NC1709Logger instance
160
+ """
161
+ global _logger
162
+
163
+ if _logger is None:
164
+ from .config import get_config
165
+ config = get_config()
166
+
167
+ # Get settings from config
168
+ verbose = config.get("ui.verbose", False)
169
+ use_colors = config.get("ui.color", True)
170
+ log_dir = Path.home() / ".nc1709" / "logs"
171
+
172
+ if level is None:
173
+ level = logging.DEBUG if verbose else logging.INFO
174
+
175
+ if log_file is None:
176
+ log_file = str(log_dir / f"nc1709_{datetime.now().strftime('%Y%m%d')}.log")
177
+
178
+ _logger = NC1709Logger(
179
+ name=name,
180
+ level=level,
181
+ log_file=log_file,
182
+ use_colors=use_colors,
183
+ use_emojis=True
184
+ )
185
+
186
+ return _logger
187
+
188
+
189
+ def reset_logger():
190
+ """Reset the global logger instance"""
191
+ global _logger
192
+ _logger = None
nc1709/mcp/__init__.py ADDED
@@ -0,0 +1,18 @@
1
+ """
2
+ NC1709 MCP (Model Context Protocol) Support
3
+ Enables integration with external tools via the MCP standard
4
+ """
5
+
6
+ from .server import MCPServer
7
+ from .client import MCPClient
8
+ from .protocol import MCPMessage, MCPTool, MCPResource
9
+ from .manager import MCPManager
10
+
11
+ __all__ = [
12
+ "MCPServer",
13
+ "MCPClient",
14
+ "MCPMessage",
15
+ "MCPTool",
16
+ "MCPResource",
17
+ "MCPManager"
18
+ ]
nc1709/mcp/client.py ADDED
@@ -0,0 +1,370 @@
1
+ """
2
+ MCP Client Implementation
3
+ Connects to external MCP servers to access their tools
4
+ """
5
+ import asyncio
6
+ import json
7
+ import subprocess
8
+ from typing import Dict, Any, Optional, List
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+
12
+ from .protocol import MCPMessage, MCPTool, MCPResource, MCPErrorCode
13
+
14
+
15
+ @dataclass
16
+ class MCPServerConnection:
17
+ """Represents a connection to an MCP server"""
18
+ name: str
19
+ command: str
20
+ args: List[str] = field(default_factory=list)
21
+ env: Dict[str, str] = field(default_factory=dict)
22
+ process: Optional[subprocess.Popen] = None
23
+ tools: List[MCPTool] = field(default_factory=list)
24
+ resources: List[MCPResource] = field(default_factory=list)
25
+ connected: bool = False
26
+
27
+
28
+ class MCPClient:
29
+ """
30
+ MCP Client for NC1709.
31
+
32
+ Connects to external MCP servers and makes their tools
33
+ available within NC1709.
34
+ """
35
+
36
+ def __init__(self):
37
+ """Initialize the MCP client"""
38
+ self._servers: Dict[str, MCPServerConnection] = {}
39
+ self._request_id = 0
40
+ self._pending_requests: Dict[int, asyncio.Future] = {}
41
+
42
+ def _next_id(self) -> int:
43
+ """Get next request ID"""
44
+ self._request_id += 1
45
+ return self._request_id
46
+
47
+ async def connect(
48
+ self,
49
+ name: str,
50
+ command: str,
51
+ args: Optional[List[str]] = None,
52
+ env: Optional[Dict[str, str]] = None
53
+ ) -> bool:
54
+ """Connect to an MCP server
55
+
56
+ Args:
57
+ name: Server name
58
+ command: Command to start the server
59
+ args: Command arguments
60
+ env: Environment variables
61
+
62
+ Returns:
63
+ True if connected successfully
64
+ """
65
+ if name in self._servers and self._servers[name].connected:
66
+ return True
67
+
68
+ args = args or []
69
+ env = env or {}
70
+
71
+ try:
72
+ # Start the server process
73
+ full_env = {**dict(subprocess.os.environ), **env}
74
+ process = subprocess.Popen(
75
+ [command] + args,
76
+ stdin=subprocess.PIPE,
77
+ stdout=subprocess.PIPE,
78
+ stderr=subprocess.PIPE,
79
+ env=full_env
80
+ )
81
+
82
+ connection = MCPServerConnection(
83
+ name=name,
84
+ command=command,
85
+ args=args,
86
+ env=env,
87
+ process=process
88
+ )
89
+
90
+ self._servers[name] = connection
91
+
92
+ # Initialize the connection
93
+ init_response = await self._send_request(
94
+ name,
95
+ "initialize",
96
+ {
97
+ "protocolVersion": "2024-11-05",
98
+ "clientInfo": {
99
+ "name": "nc1709",
100
+ "version": "1.0.0"
101
+ },
102
+ "capabilities": {}
103
+ }
104
+ )
105
+
106
+ if init_response and "error" not in init_response:
107
+ connection.connected = True
108
+
109
+ # Fetch available tools
110
+ tools_response = await self._send_request(name, "tools/list", {})
111
+ if tools_response and "tools" in tools_response:
112
+ connection.tools = [
113
+ MCPTool(
114
+ name=t["name"],
115
+ description=t.get("description", ""),
116
+ parameters=[] # Parse inputSchema if needed
117
+ )
118
+ for t in tools_response["tools"]
119
+ ]
120
+
121
+ # Fetch available resources
122
+ resources_response = await self._send_request(name, "resources/list", {})
123
+ if resources_response and "resources" in resources_response:
124
+ connection.resources = [
125
+ MCPResource(
126
+ uri=r["uri"],
127
+ name=r["name"],
128
+ description=r.get("description", "")
129
+ )
130
+ for r in resources_response["resources"]
131
+ ]
132
+
133
+ return True
134
+
135
+ return False
136
+
137
+ except Exception as e:
138
+ print(f"Failed to connect to MCP server {name}: {e}")
139
+ return False
140
+
141
+ async def disconnect(self, name: str) -> bool:
142
+ """Disconnect from an MCP server
143
+
144
+ Args:
145
+ name: Server name
146
+
147
+ Returns:
148
+ True if disconnected
149
+ """
150
+ if name not in self._servers:
151
+ return False
152
+
153
+ connection = self._servers[name]
154
+
155
+ if connection.process:
156
+ connection.process.terminate()
157
+ try:
158
+ connection.process.wait(timeout=5)
159
+ except subprocess.TimeoutExpired:
160
+ connection.process.kill()
161
+
162
+ connection.connected = False
163
+ del self._servers[name]
164
+ return True
165
+
166
+ async def disconnect_all(self) -> None:
167
+ """Disconnect from all servers"""
168
+ for name in list(self._servers.keys()):
169
+ await self.disconnect(name)
170
+
171
+ async def call_tool(
172
+ self,
173
+ server_name: str,
174
+ tool_name: str,
175
+ arguments: Optional[Dict[str, Any]] = None
176
+ ) -> Dict[str, Any]:
177
+ """Call a tool on an MCP server
178
+
179
+ Args:
180
+ server_name: Server name
181
+ tool_name: Tool name
182
+ arguments: Tool arguments
183
+
184
+ Returns:
185
+ Tool result
186
+ """
187
+ if server_name not in self._servers:
188
+ return {"error": f"Server not connected: {server_name}"}
189
+
190
+ connection = self._servers[server_name]
191
+ if not connection.connected:
192
+ return {"error": f"Server not connected: {server_name}"}
193
+
194
+ response = await self._send_request(
195
+ server_name,
196
+ "tools/call",
197
+ {
198
+ "name": tool_name,
199
+ "arguments": arguments or {}
200
+ }
201
+ )
202
+
203
+ return response or {"error": "No response from server"}
204
+
205
+ async def read_resource(
206
+ self,
207
+ server_name: str,
208
+ uri: str
209
+ ) -> Dict[str, Any]:
210
+ """Read a resource from an MCP server
211
+
212
+ Args:
213
+ server_name: Server name
214
+ uri: Resource URI
215
+
216
+ Returns:
217
+ Resource contents
218
+ """
219
+ if server_name not in self._servers:
220
+ return {"error": f"Server not connected: {server_name}"}
221
+
222
+ response = await self._send_request(
223
+ server_name,
224
+ "resources/read",
225
+ {"uri": uri}
226
+ )
227
+
228
+ return response or {"error": "No response from server"}
229
+
230
+ def list_servers(self) -> List[Dict[str, Any]]:
231
+ """List connected servers
232
+
233
+ Returns:
234
+ List of server info dicts
235
+ """
236
+ return [
237
+ {
238
+ "name": name,
239
+ "connected": conn.connected,
240
+ "tools": len(conn.tools),
241
+ "resources": len(conn.resources)
242
+ }
243
+ for name, conn in self._servers.items()
244
+ ]
245
+
246
+ def get_tools(self, server_name: Optional[str] = None) -> List[MCPTool]:
247
+ """Get available tools
248
+
249
+ Args:
250
+ server_name: Filter by server (None for all)
251
+
252
+ Returns:
253
+ List of tools
254
+ """
255
+ if server_name:
256
+ if server_name in self._servers:
257
+ return self._servers[server_name].tools
258
+ return []
259
+
260
+ all_tools = []
261
+ for conn in self._servers.values():
262
+ all_tools.extend(conn.tools)
263
+ return all_tools
264
+
265
+ def get_resources(self, server_name: Optional[str] = None) -> List[MCPResource]:
266
+ """Get available resources
267
+
268
+ Args:
269
+ server_name: Filter by server (None for all)
270
+
271
+ Returns:
272
+ List of resources
273
+ """
274
+ if server_name:
275
+ if server_name in self._servers:
276
+ return self._servers[server_name].resources
277
+ return []
278
+
279
+ all_resources = []
280
+ for conn in self._servers.values():
281
+ all_resources.extend(conn.resources)
282
+ return all_resources
283
+
284
+ async def _send_request(
285
+ self,
286
+ server_name: str,
287
+ method: str,
288
+ params: Dict[str, Any]
289
+ ) -> Optional[Dict[str, Any]]:
290
+ """Send a request to an MCP server
291
+
292
+ Args:
293
+ server_name: Server name
294
+ method: Method name
295
+ params: Request parameters
296
+
297
+ Returns:
298
+ Response data or None
299
+ """
300
+ if server_name not in self._servers:
301
+ return None
302
+
303
+ connection = self._servers[server_name]
304
+ if not connection.process:
305
+ return None
306
+
307
+ request_id = self._next_id()
308
+ request = MCPMessage.request(request_id, method, params)
309
+
310
+ try:
311
+ # Send request
312
+ request_json = request.to_json() + "\n"
313
+ connection.process.stdin.write(request_json.encode())
314
+ connection.process.stdin.flush()
315
+
316
+ # Read response (with timeout)
317
+ # Note: This is a simplified sync implementation
318
+ # A production version would use proper async I/O
319
+ response_line = connection.process.stdout.readline()
320
+ if response_line:
321
+ response = MCPMessage.from_json(response_line.decode())
322
+ if response.error:
323
+ return {"error": response.error}
324
+ return response.result
325
+
326
+ except Exception as e:
327
+ print(f"Error communicating with MCP server {server_name}: {e}")
328
+
329
+ return None
330
+
331
+ async def auto_discover(self, config_path: Optional[str] = None) -> int:
332
+ """Auto-discover MCP servers from configuration
333
+
334
+ Args:
335
+ config_path: Path to MCP config file
336
+
337
+ Returns:
338
+ Number of servers discovered
339
+ """
340
+ if config_path is None:
341
+ # Look for standard config locations
342
+ config_paths = [
343
+ Path.home() / ".nc1709" / "mcp.json",
344
+ Path.cwd() / ".nc1709" / "mcp.json",
345
+ Path.cwd() / "mcp.json"
346
+ ]
347
+ else:
348
+ config_paths = [Path(config_path)]
349
+
350
+ count = 0
351
+
352
+ for path in config_paths:
353
+ if path.exists():
354
+ try:
355
+ config = json.loads(path.read_text())
356
+ servers = config.get("mcpServers", {})
357
+
358
+ for name, server_config in servers.items():
359
+ command = server_config.get("command")
360
+ args = server_config.get("args", [])
361
+ env = server_config.get("env", {})
362
+
363
+ if command:
364
+ if await self.connect(name, command, args, env):
365
+ count += 1
366
+
367
+ except Exception as e:
368
+ print(f"Error loading MCP config from {path}: {e}")
369
+
370
+ return count