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
@@ -0,0 +1,245 @@
1
+ """
2
+ MCP Bridge
3
+
4
+ Integrates MCP (Model Context Protocol) servers into the agent's tool registry.
5
+ Allows the agent to use tools from external MCP servers.
6
+ """
7
+
8
+ import asyncio
9
+ from typing import Any, Dict, List, Optional
10
+
11
+ from .tools.base import Tool, ToolResult, ToolParameter, ToolPermission, ToolRegistry
12
+
13
+
14
+ class MCPTool(Tool):
15
+ """
16
+ A tool that wraps an MCP server tool.
17
+
18
+ Allows MCP tools to be used seamlessly alongside built-in tools.
19
+ """
20
+
21
+ category = "mcp"
22
+ permission = ToolPermission.ASK # Ask before using MCP tools
23
+
24
+ def __init__(
25
+ self,
26
+ name: str,
27
+ description: str,
28
+ parameters: List[ToolParameter],
29
+ mcp_manager,
30
+ server_name: str,
31
+ ):
32
+ """Initialize MCP tool wrapper
33
+
34
+ Args:
35
+ name: Tool name
36
+ description: Tool description
37
+ parameters: Tool parameters
38
+ mcp_manager: MCP manager for executing calls
39
+ server_name: Name of the MCP server providing this tool
40
+ """
41
+ super().__init__()
42
+ self.name = name
43
+ self.description = description
44
+ self.parameters = parameters
45
+ self._mcp_manager = mcp_manager
46
+ self._server_name = server_name
47
+
48
+ def execute(self, **kwargs) -> ToolResult:
49
+ """Execute the MCP tool"""
50
+ try:
51
+ # Run async MCP call synchronously
52
+ loop = asyncio.new_event_loop()
53
+ asyncio.set_event_loop(loop)
54
+ try:
55
+ result = loop.run_until_complete(
56
+ self._mcp_manager.call_tool(self.name, kwargs)
57
+ )
58
+ finally:
59
+ loop.close()
60
+
61
+ # Parse MCP result
62
+ if "error" in result:
63
+ return ToolResult(
64
+ success=False,
65
+ output="",
66
+ error=result["error"],
67
+ target=str(kwargs)[:30],
68
+ )
69
+
70
+ # Extract content
71
+ output = ""
72
+ if "content" in result:
73
+ for item in result["content"]:
74
+ if item.get("type") == "text":
75
+ output += item.get("text", "")
76
+
77
+ return ToolResult(
78
+ success=True,
79
+ output=output or str(result),
80
+ target=str(kwargs)[:30],
81
+ data=result,
82
+ )
83
+
84
+ except Exception as e:
85
+ return ToolResult(
86
+ success=False,
87
+ output="",
88
+ error=f"MCP tool error: {e}",
89
+ target=str(kwargs)[:30],
90
+ )
91
+
92
+
93
+ class MCPBridge:
94
+ """
95
+ Bridge between MCP servers and the agent tool registry.
96
+
97
+ Discovers tools from MCP servers and registers them as agent tools.
98
+ """
99
+
100
+ def __init__(self, registry: ToolRegistry = None):
101
+ """Initialize the MCP bridge
102
+
103
+ Args:
104
+ registry: Tool registry to add MCP tools to
105
+ """
106
+ self.registry = registry or ToolRegistry()
107
+ self._mcp_manager = None
108
+ self._registered_tools: Dict[str, MCPTool] = {}
109
+
110
+ def connect_mcp_manager(self, mcp_manager) -> None:
111
+ """Connect to an MCP manager
112
+
113
+ Args:
114
+ mcp_manager: MCPManager instance
115
+ """
116
+ self._mcp_manager = mcp_manager
117
+
118
+ def discover_and_register_tools(self) -> int:
119
+ """Discover tools from MCP servers and register them
120
+
121
+ Returns:
122
+ Number of tools registered
123
+ """
124
+ if not self._mcp_manager:
125
+ return 0
126
+
127
+ count = 0
128
+
129
+ try:
130
+ # Get all tools from MCP manager
131
+ all_tools = self._mcp_manager.get_all_tools()
132
+
133
+ # Register local tools
134
+ for tool in all_tools.get("local", []):
135
+ mcp_tool = self._create_mcp_tool(tool, "local")
136
+ if mcp_tool:
137
+ self.registry.register(mcp_tool)
138
+ self._registered_tools[mcp_tool.name] = mcp_tool
139
+ count += 1
140
+
141
+ # Register remote tools
142
+ for tool in all_tools.get("remote", []):
143
+ mcp_tool = self._create_mcp_tool(tool, "remote")
144
+ if mcp_tool:
145
+ self.registry.register(mcp_tool)
146
+ self._registered_tools[mcp_tool.name] = mcp_tool
147
+ count += 1
148
+
149
+ except Exception as e:
150
+ print(f"Error discovering MCP tools: {e}")
151
+
152
+ return count
153
+
154
+ def _create_mcp_tool(self, mcp_tool_def, server_name: str) -> Optional[MCPTool]:
155
+ """Create an MCPTool from an MCP tool definition
156
+
157
+ Args:
158
+ mcp_tool_def: MCP tool definition object
159
+ server_name: Name of providing server
160
+
161
+ Returns:
162
+ MCPTool instance or None
163
+ """
164
+ try:
165
+ # Extract tool info
166
+ name = mcp_tool_def.name
167
+ description = mcp_tool_def.description or f"MCP tool: {name}"
168
+
169
+ # Convert parameters
170
+ parameters = []
171
+ if hasattr(mcp_tool_def, 'parameters') and mcp_tool_def.parameters:
172
+ for param in mcp_tool_def.parameters:
173
+ parameters.append(ToolParameter(
174
+ name=param.name,
175
+ description=param.description or param.name,
176
+ type=self._map_type(param.type) if hasattr(param, 'type') else "string",
177
+ required=getattr(param, 'required', True),
178
+ ))
179
+
180
+ # Prefix MCP tool names to avoid conflicts
181
+ prefixed_name = f"mcp_{name}"
182
+
183
+ return MCPTool(
184
+ name=prefixed_name,
185
+ description=description,
186
+ parameters=parameters,
187
+ mcp_manager=self._mcp_manager,
188
+ server_name=server_name,
189
+ )
190
+
191
+ except Exception as e:
192
+ print(f"Error creating MCP tool: {e}")
193
+ return None
194
+
195
+ def _map_type(self, mcp_type: str) -> str:
196
+ """Map MCP type to tool parameter type"""
197
+ type_map = {
198
+ "string": "string",
199
+ "str": "string",
200
+ "int": "integer",
201
+ "integer": "integer",
202
+ "float": "number",
203
+ "number": "number",
204
+ "bool": "boolean",
205
+ "boolean": "boolean",
206
+ "list": "array",
207
+ "array": "array",
208
+ "dict": "object",
209
+ "object": "object",
210
+ }
211
+ return type_map.get(mcp_type.lower(), "string")
212
+
213
+ def get_registered_tools(self) -> List[str]:
214
+ """Get list of registered MCP tool names"""
215
+ return list(self._registered_tools.keys())
216
+
217
+ def unregister_all(self) -> int:
218
+ """Unregister all MCP tools
219
+
220
+ Returns:
221
+ Number of tools unregistered
222
+ """
223
+ count = 0
224
+ for name in list(self._registered_tools.keys()):
225
+ if self.registry.unregister(name):
226
+ count += 1
227
+ del self._registered_tools[name]
228
+ return count
229
+
230
+
231
+ def integrate_mcp_with_agent(agent, mcp_manager) -> MCPBridge:
232
+ """Integrate MCP tools with an agent
233
+
234
+ Args:
235
+ agent: Agent instance
236
+ mcp_manager: MCPManager instance
237
+
238
+ Returns:
239
+ MCPBridge instance
240
+ """
241
+ bridge = MCPBridge(agent.registry)
242
+ bridge.connect_mcp_manager(mcp_manager)
243
+ count = bridge.discover_and_register_tools()
244
+ print(f"Registered {count} MCP tools with agent")
245
+ return bridge
@@ -0,0 +1,298 @@
1
+ """
2
+ Tool Permissions System
3
+
4
+ Manages permissions for tool execution with configurable policies.
5
+ """
6
+
7
+ import json
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional, Set
12
+
13
+ from .tools.base import ToolPermission, ToolRegistry
14
+
15
+
16
+ class PermissionPolicy(Enum):
17
+ """Permission policies for different contexts"""
18
+ STRICT = "strict" # Ask for everything
19
+ NORMAL = "normal" # Ask for writes/executes, auto for reads
20
+ PERMISSIVE = "permissive" # Auto-approve most, ask for dangerous
21
+ TRUST = "trust" # Auto-approve everything
22
+
23
+
24
+ @dataclass
25
+ class PermissionRule:
26
+ """A rule for tool permissions"""
27
+ tool_pattern: str # Tool name or pattern (supports wildcards)
28
+ permission: ToolPermission
29
+ reason: Optional[str] = None
30
+
31
+
32
+ @dataclass
33
+ class PermissionConfig:
34
+ """Configuration for the permissions system"""
35
+ policy: PermissionPolicy = PermissionPolicy.NORMAL
36
+ custom_rules: List[PermissionRule] = field(default_factory=list)
37
+ blocked_tools: Set[str] = field(default_factory=set)
38
+ session_approvals: Set[str] = field(default_factory=set)
39
+
40
+
41
+ class PermissionManager:
42
+ """
43
+ Manages tool permissions with configurable policies.
44
+
45
+ Provides:
46
+ - Policy-based default permissions
47
+ - Custom per-tool rules
48
+ - Session-level approvals
49
+ - Blocking of dangerous tools
50
+ """
51
+
52
+ # Default permissions by policy
53
+ POLICY_DEFAULTS = {
54
+ PermissionPolicy.STRICT: {
55
+ "default": ToolPermission.ASK,
56
+ "auto": [],
57
+ },
58
+ PermissionPolicy.NORMAL: {
59
+ "default": ToolPermission.ASK,
60
+ "auto": ["Read", "Glob", "Grep", "Ripgrep", "TodoWrite"],
61
+ },
62
+ PermissionPolicy.PERMISSIVE: {
63
+ "default": ToolPermission.AUTO,
64
+ "ask": ["Bash", "Write", "Edit", "Task", "BackgroundBash"],
65
+ },
66
+ PermissionPolicy.TRUST: {
67
+ "default": ToolPermission.AUTO,
68
+ "ask": [],
69
+ },
70
+ }
71
+
72
+ def __init__(self, config: PermissionConfig = None):
73
+ """Initialize the permission manager
74
+
75
+ Args:
76
+ config: Permission configuration
77
+ """
78
+ self.config = config or PermissionConfig()
79
+ self._registry: Optional[ToolRegistry] = None
80
+
81
+ def attach_registry(self, registry: ToolRegistry) -> None:
82
+ """Attach a tool registry and apply permissions
83
+
84
+ Args:
85
+ registry: Tool registry to manage
86
+ """
87
+ self._registry = registry
88
+ self._apply_policy()
89
+ self._apply_custom_rules()
90
+
91
+ def _apply_policy(self) -> None:
92
+ """Apply the current policy to the registry"""
93
+ if not self._registry:
94
+ return
95
+
96
+ policy_config = self.POLICY_DEFAULTS.get(self.config.policy, {})
97
+ default_perm = policy_config.get("default", ToolPermission.ASK)
98
+
99
+ # Set default for all tools
100
+ for tool_name in self._registry.list_names():
101
+ self._registry.set_permission(tool_name, default_perm)
102
+
103
+ # Apply auto-approve list
104
+ for tool_name in policy_config.get("auto", []):
105
+ self._registry.set_permission(tool_name, ToolPermission.AUTO)
106
+
107
+ # Apply ask list
108
+ for tool_name in policy_config.get("ask", []):
109
+ self._registry.set_permission(tool_name, ToolPermission.ASK)
110
+
111
+ def _apply_custom_rules(self) -> None:
112
+ """Apply custom permission rules"""
113
+ if not self._registry:
114
+ return
115
+
116
+ for rule in self.config.custom_rules:
117
+ # Handle wildcards
118
+ if "*" in rule.tool_pattern:
119
+ import fnmatch
120
+ for tool_name in self._registry.list_names():
121
+ if fnmatch.fnmatch(tool_name, rule.tool_pattern):
122
+ self._registry.set_permission(tool_name, rule.permission)
123
+ else:
124
+ self._registry.set_permission(rule.tool_pattern, rule.permission)
125
+
126
+ # Apply blocked tools
127
+ for tool_name in self.config.blocked_tools:
128
+ self._registry.set_permission(tool_name, ToolPermission.DENY)
129
+
130
+ def set_policy(self, policy: PermissionPolicy) -> None:
131
+ """Change the permission policy
132
+
133
+ Args:
134
+ policy: New policy to apply
135
+ """
136
+ self.config.policy = policy
137
+ self._apply_policy()
138
+ self._apply_custom_rules()
139
+
140
+ def add_rule(self, rule: PermissionRule) -> None:
141
+ """Add a custom permission rule
142
+
143
+ Args:
144
+ rule: Rule to add
145
+ """
146
+ self.config.custom_rules.append(rule)
147
+ if self._registry:
148
+ self._apply_custom_rules()
149
+
150
+ def block_tool(self, tool_name: str) -> None:
151
+ """Block a tool from being used
152
+
153
+ Args:
154
+ tool_name: Tool to block
155
+ """
156
+ self.config.blocked_tools.add(tool_name)
157
+ if self._registry:
158
+ self._registry.set_permission(tool_name, ToolPermission.DENY)
159
+
160
+ def unblock_tool(self, tool_name: str) -> None:
161
+ """Unblock a tool
162
+
163
+ Args:
164
+ tool_name: Tool to unblock
165
+ """
166
+ self.config.blocked_tools.discard(tool_name)
167
+ if self._registry:
168
+ self._apply_policy()
169
+ self._apply_custom_rules()
170
+
171
+ def approve_for_session(self, tool_name: str) -> None:
172
+ """Approve a tool for the current session
173
+
174
+ Args:
175
+ tool_name: Tool to approve
176
+ """
177
+ self.config.session_approvals.add(tool_name)
178
+ if self._registry:
179
+ self._registry.approve_for_session(tool_name)
180
+
181
+ def clear_session_approvals(self) -> None:
182
+ """Clear all session approvals"""
183
+ self.config.session_approvals.clear()
184
+ if self._registry:
185
+ self._registry.clear_session_approvals()
186
+
187
+ def needs_approval(self, tool_name: str) -> bool:
188
+ """Check if a tool needs approval
189
+
190
+ Args:
191
+ tool_name: Tool to check
192
+
193
+ Returns:
194
+ True if approval is needed
195
+ """
196
+ if not self._registry:
197
+ return True
198
+
199
+ if tool_name in self.config.blocked_tools:
200
+ return True # Will be denied
201
+
202
+ if tool_name in self.config.session_approvals:
203
+ return False
204
+
205
+ return self._registry.needs_approval(tool_name)
206
+
207
+ def get_permission(self, tool_name: str) -> ToolPermission:
208
+ """Get the current permission for a tool
209
+
210
+ Args:
211
+ tool_name: Tool name
212
+
213
+ Returns:
214
+ Current permission level
215
+ """
216
+ if tool_name in self.config.blocked_tools:
217
+ return ToolPermission.DENY
218
+
219
+ if self._registry:
220
+ return self._registry.get_permission(tool_name)
221
+
222
+ return ToolPermission.ASK
223
+
224
+ def get_status(self) -> Dict:
225
+ """Get current permission status
226
+
227
+ Returns:
228
+ Dict with permission information
229
+ """
230
+ tools_by_permission = {
231
+ "auto": [],
232
+ "ask": [],
233
+ "ask_once": [],
234
+ "deny": [],
235
+ }
236
+
237
+ if self._registry:
238
+ for tool_name in self._registry.list_names():
239
+ perm = self.get_permission(tool_name)
240
+ tools_by_permission[perm.value].append(tool_name)
241
+
242
+ return {
243
+ "policy": self.config.policy.value,
244
+ "tools": tools_by_permission,
245
+ "blocked": list(self.config.blocked_tools),
246
+ "session_approved": list(self.config.session_approvals),
247
+ "custom_rules": len(self.config.custom_rules),
248
+ }
249
+
250
+ def save_config(self, path: str) -> None:
251
+ """Save permission configuration to file
252
+
253
+ Args:
254
+ path: Path to save config
255
+ """
256
+ config_data = {
257
+ "policy": self.config.policy.value,
258
+ "custom_rules": [
259
+ {
260
+ "tool_pattern": r.tool_pattern,
261
+ "permission": r.permission.value,
262
+ "reason": r.reason,
263
+ }
264
+ for r in self.config.custom_rules
265
+ ],
266
+ "blocked_tools": list(self.config.blocked_tools),
267
+ }
268
+
269
+ with open(path, "w") as f:
270
+ json.dump(config_data, f, indent=2)
271
+
272
+ @classmethod
273
+ def load_config(cls, path: str) -> "PermissionManager":
274
+ """Load permission configuration from file
275
+
276
+ Args:
277
+ path: Path to config file
278
+
279
+ Returns:
280
+ PermissionManager with loaded config
281
+ """
282
+ with open(path, "r") as f:
283
+ data = json.load(f)
284
+
285
+ config = PermissionConfig(
286
+ policy=PermissionPolicy(data.get("policy", "normal")),
287
+ custom_rules=[
288
+ PermissionRule(
289
+ tool_pattern=r["tool_pattern"],
290
+ permission=ToolPermission(r["permission"]),
291
+ reason=r.get("reason"),
292
+ )
293
+ for r in data.get("custom_rules", [])
294
+ ],
295
+ blocked_tools=set(data.get("blocked_tools", [])),
296
+ )
297
+
298
+ return cls(config)
@@ -0,0 +1,21 @@
1
+ """
2
+ NC1709 Agent Tools
3
+
4
+ Built-in tools for the agentic CLI:
5
+ - File operations: Read, Write, Edit, Glob
6
+ - Search operations: Grep, Ripgrep
7
+ - Execution: Bash, BackgroundBash
8
+ - Sub-agents: Task, TodoWrite, AskUser
9
+ - Web: WebFetch, WebSearch, WebScreenshot
10
+ - Notebook: NotebookRead, NotebookEdit, NotebookRun
11
+ """
12
+
13
+ from .base import Tool, ToolResult, ToolRegistry, ToolPermission, ToolParameter
14
+
15
+ __all__ = [
16
+ "Tool",
17
+ "ToolResult",
18
+ "ToolRegistry",
19
+ "ToolPermission",
20
+ "ToolParameter",
21
+ ]