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,516 @@
1
+ """
2
+ Jupyter Notebook Tools
3
+
4
+ Tools for working with Jupyter notebooks (.ipynb files):
5
+ - NotebookRead: Read notebook contents with cells and outputs
6
+ - NotebookEdit: Edit, insert, or delete notebook cells
7
+ """
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Optional, List, Dict, Any
12
+
13
+ from .base import Tool, ToolResult, ToolParameter, ToolPermission
14
+
15
+
16
+ class NotebookReadTool(Tool):
17
+ """Read contents of a Jupyter notebook"""
18
+
19
+ name = "NotebookRead"
20
+ description = (
21
+ "Read the contents of a Jupyter notebook (.ipynb file). "
22
+ "Returns all cells with their types (code/markdown), source, and outputs."
23
+ )
24
+ category = "notebook"
25
+ permission = ToolPermission.AUTO # Safe to auto-execute
26
+
27
+ parameters = [
28
+ ToolParameter(
29
+ name="notebook_path",
30
+ description="The absolute path to the Jupyter notebook file",
31
+ type="string",
32
+ required=True,
33
+ ),
34
+ ToolParameter(
35
+ name="cell_numbers",
36
+ description="Optional list of specific cell numbers to read (0-indexed). If not provided, reads all cells.",
37
+ type="array",
38
+ required=False,
39
+ default=None,
40
+ ),
41
+ ToolParameter(
42
+ name="include_outputs",
43
+ description="Whether to include cell outputs in the result (default: true)",
44
+ type="boolean",
45
+ required=False,
46
+ default=True,
47
+ ),
48
+ ]
49
+
50
+ def execute(
51
+ self,
52
+ notebook_path: str,
53
+ cell_numbers: List[int] = None,
54
+ include_outputs: bool = True,
55
+ ) -> ToolResult:
56
+ """Read notebook contents"""
57
+ path = Path(notebook_path).expanduser()
58
+
59
+ # Check if file exists
60
+ if not path.exists():
61
+ return ToolResult(
62
+ success=False,
63
+ output="",
64
+ error=f"Notebook not found: {notebook_path}",
65
+ target=notebook_path,
66
+ )
67
+
68
+ # Check file extension
69
+ if path.suffix.lower() != ".ipynb":
70
+ return ToolResult(
71
+ success=False,
72
+ output="",
73
+ error=f"Not a Jupyter notebook file (expected .ipynb): {notebook_path}",
74
+ target=notebook_path,
75
+ )
76
+
77
+ try:
78
+ # Read and parse notebook
79
+ with open(path, "r", encoding="utf-8") as f:
80
+ notebook = json.load(f)
81
+
82
+ cells = notebook.get("cells", [])
83
+ total_cells = len(cells)
84
+
85
+ # Filter to specific cells if requested
86
+ if cell_numbers:
87
+ selected_indices = [i for i in cell_numbers if 0 <= i < total_cells]
88
+ if not selected_indices:
89
+ return ToolResult(
90
+ success=False,
91
+ output="",
92
+ error=f"No valid cell numbers in range 0-{total_cells-1}",
93
+ target=notebook_path,
94
+ )
95
+ else:
96
+ selected_indices = list(range(total_cells))
97
+
98
+ # Format output
99
+ output_parts = [
100
+ f"Notebook: {notebook_path}",
101
+ f"Total cells: {total_cells}",
102
+ f"Kernel: {notebook.get('metadata', {}).get('kernelspec', {}).get('display_name', 'Unknown')}",
103
+ "=" * 60,
104
+ ]
105
+
106
+ for idx in selected_indices:
107
+ cell = cells[idx]
108
+ cell_type = cell.get("cell_type", "unknown")
109
+ source = "".join(cell.get("source", []))
110
+ cell_id = cell.get("id", f"cell_{idx}")
111
+
112
+ output_parts.append(f"\n[Cell {idx}] ({cell_type}) id={cell_id}")
113
+ output_parts.append("-" * 40)
114
+
115
+ # Show source with line numbers
116
+ for i, line in enumerate(source.split("\n"), 1):
117
+ output_parts.append(f" {i:3}│ {line}")
118
+
119
+ # Include outputs for code cells
120
+ if include_outputs and cell_type == "code":
121
+ outputs = cell.get("outputs", [])
122
+ if outputs:
123
+ output_parts.append("\n Output:")
124
+ for out in outputs[:5]: # Limit outputs shown
125
+ out_type = out.get("output_type", "")
126
+ if out_type == "stream":
127
+ text = "".join(out.get("text", []))[:500]
128
+ output_parts.append(f" [stream] {text}")
129
+ elif out_type == "execute_result":
130
+ data = out.get("data", {})
131
+ if "text/plain" in data:
132
+ text = "".join(data["text/plain"])[:500]
133
+ output_parts.append(f" [result] {text}")
134
+ elif out_type == "error":
135
+ ename = out.get("ename", "Error")
136
+ evalue = out.get("evalue", "")[:200]
137
+ output_parts.append(f" [error] {ename}: {evalue}")
138
+ elif out_type == "display_data":
139
+ data_types = list(out.get("data", {}).keys())
140
+ output_parts.append(f" [display] types: {', '.join(data_types)}")
141
+
142
+ return ToolResult(
143
+ success=True,
144
+ output="\n".join(output_parts),
145
+ target=notebook_path,
146
+ data={
147
+ "total_cells": total_cells,
148
+ "cells_shown": len(selected_indices),
149
+ "kernel": notebook.get("metadata", {}).get("kernelspec", {}).get("name"),
150
+ },
151
+ )
152
+
153
+ except json.JSONDecodeError as e:
154
+ return ToolResult(
155
+ success=False,
156
+ output="",
157
+ error=f"Invalid notebook JSON: {e}",
158
+ target=notebook_path,
159
+ )
160
+ except Exception as e:
161
+ return ToolResult(
162
+ success=False,
163
+ output="",
164
+ error=f"Error reading notebook: {e}",
165
+ target=notebook_path,
166
+ )
167
+
168
+
169
+ class NotebookEditTool(Tool):
170
+ """Edit a cell in a Jupyter notebook"""
171
+
172
+ name = "NotebookEdit"
173
+ description = (
174
+ "Edit, insert, or delete a cell in a Jupyter notebook. "
175
+ "Use edit_mode='replace' to update a cell, 'insert' to add a new cell, "
176
+ "or 'delete' to remove a cell."
177
+ )
178
+ category = "notebook"
179
+ permission = ToolPermission.ASK # Ask before modifying notebooks
180
+
181
+ parameters = [
182
+ ToolParameter(
183
+ name="notebook_path",
184
+ description="The absolute path to the Jupyter notebook file",
185
+ type="string",
186
+ required=True,
187
+ ),
188
+ ToolParameter(
189
+ name="cell_number",
190
+ description="The cell number to edit (0-indexed). For insert, new cell is added after this position.",
191
+ type="integer",
192
+ required=True,
193
+ ),
194
+ ToolParameter(
195
+ name="new_source",
196
+ description="The new source code/content for the cell",
197
+ type="string",
198
+ required=True,
199
+ ),
200
+ ToolParameter(
201
+ name="cell_type",
202
+ description="Cell type: 'code' or 'markdown'. Required for insert, optional for replace.",
203
+ type="string",
204
+ required=False,
205
+ default=None,
206
+ ),
207
+ ToolParameter(
208
+ name="edit_mode",
209
+ description="Edit mode: 'replace' (update cell), 'insert' (add new cell), 'delete' (remove cell)",
210
+ type="string",
211
+ required=False,
212
+ default="replace",
213
+ ),
214
+ ]
215
+
216
+ def execute(
217
+ self,
218
+ notebook_path: str,
219
+ cell_number: int,
220
+ new_source: str,
221
+ cell_type: str = None,
222
+ edit_mode: str = "replace",
223
+ ) -> ToolResult:
224
+ """Edit notebook cell"""
225
+ path = Path(notebook_path).expanduser()
226
+
227
+ # Validate edit_mode
228
+ if edit_mode not in ["replace", "insert", "delete"]:
229
+ return ToolResult(
230
+ success=False,
231
+ output="",
232
+ error=f"Invalid edit_mode: {edit_mode}. Use 'replace', 'insert', or 'delete'.",
233
+ target=notebook_path,
234
+ )
235
+
236
+ # Check if file exists
237
+ if not path.exists():
238
+ return ToolResult(
239
+ success=False,
240
+ output="",
241
+ error=f"Notebook not found: {notebook_path}",
242
+ target=notebook_path,
243
+ )
244
+
245
+ try:
246
+ # Read notebook
247
+ with open(path, "r", encoding="utf-8") as f:
248
+ notebook = json.load(f)
249
+
250
+ cells = notebook.get("cells", [])
251
+ total_cells = len(cells)
252
+
253
+ # Validate cell number
254
+ if edit_mode == "insert":
255
+ # For insert, -1 means at the beginning, cell_number means after that cell
256
+ if cell_number < -1 or cell_number >= total_cells:
257
+ return ToolResult(
258
+ success=False,
259
+ output="",
260
+ error=f"Invalid cell number for insert: {cell_number}. Use -1 to {total_cells-1}.",
261
+ target=notebook_path,
262
+ )
263
+ else:
264
+ if cell_number < 0 or cell_number >= total_cells:
265
+ return ToolResult(
266
+ success=False,
267
+ output="",
268
+ error=f"Cell number {cell_number} out of range. Notebook has {total_cells} cells (0-{total_cells-1}).",
269
+ target=notebook_path,
270
+ )
271
+
272
+ # Perform the edit
273
+ if edit_mode == "delete":
274
+ deleted_cell = cells.pop(cell_number)
275
+ action_msg = f"Deleted cell {cell_number} ({deleted_cell.get('cell_type', 'unknown')})"
276
+
277
+ elif edit_mode == "insert":
278
+ # Determine cell type
279
+ if not cell_type:
280
+ cell_type = "code" # Default to code cell
281
+ if cell_type not in ["code", "markdown"]:
282
+ return ToolResult(
283
+ success=False,
284
+ output="",
285
+ error=f"Invalid cell_type: {cell_type}. Use 'code' or 'markdown'.",
286
+ target=notebook_path,
287
+ )
288
+
289
+ # Create new cell
290
+ new_cell = self._create_cell(cell_type, new_source)
291
+ insert_pos = cell_number + 1 # Insert after the specified cell
292
+ cells.insert(insert_pos, new_cell)
293
+ action_msg = f"Inserted new {cell_type} cell at position {insert_pos}"
294
+
295
+ else: # replace
296
+ old_cell = cells[cell_number]
297
+ old_source = "".join(old_cell.get("source", []))
298
+
299
+ # Update cell type if specified
300
+ if cell_type and cell_type in ["code", "markdown"]:
301
+ old_cell["cell_type"] = cell_type
302
+
303
+ # Update source
304
+ old_cell["source"] = new_source.split("\n")
305
+ # Add newlines except for last line
306
+ old_cell["source"] = [
307
+ line + "\n" if i < len(old_cell["source"]) - 1 else line
308
+ for i, line in enumerate(old_cell["source"])
309
+ ]
310
+
311
+ # Clear outputs for code cells
312
+ if old_cell.get("cell_type") == "code":
313
+ old_cell["outputs"] = []
314
+ old_cell["execution_count"] = None
315
+
316
+ action_msg = f"Replaced cell {cell_number} ({old_cell.get('cell_type', 'unknown')})"
317
+
318
+ # Write back
319
+ with open(path, "w", encoding="utf-8") as f:
320
+ json.dump(notebook, f, indent=1)
321
+
322
+ return ToolResult(
323
+ success=True,
324
+ output=f"{action_msg}\nNotebook saved: {notebook_path}",
325
+ target=f"{notebook_path}:cell_{cell_number}",
326
+ data={
327
+ "action": edit_mode,
328
+ "cell_number": cell_number,
329
+ "total_cells": len(cells),
330
+ },
331
+ )
332
+
333
+ except json.JSONDecodeError as e:
334
+ return ToolResult(
335
+ success=False,
336
+ output="",
337
+ error=f"Invalid notebook JSON: {e}",
338
+ target=notebook_path,
339
+ )
340
+ except Exception as e:
341
+ return ToolResult(
342
+ success=False,
343
+ output="",
344
+ error=f"Error editing notebook: {e}",
345
+ target=notebook_path,
346
+ )
347
+
348
+ def _create_cell(self, cell_type: str, source: str) -> Dict[str, Any]:
349
+ """Create a new notebook cell"""
350
+ import uuid
351
+
352
+ source_lines = source.split("\n")
353
+ # Add newlines except for last line
354
+ source_lines = [
355
+ line + "\n" if i < len(source_lines) - 1 else line
356
+ for i, line in enumerate(source_lines)
357
+ ]
358
+
359
+ cell = {
360
+ "cell_type": cell_type,
361
+ "id": str(uuid.uuid4())[:8],
362
+ "metadata": {},
363
+ "source": source_lines,
364
+ }
365
+
366
+ if cell_type == "code":
367
+ cell["execution_count"] = None
368
+ cell["outputs"] = []
369
+
370
+ return cell
371
+
372
+
373
+ class NotebookRunTool(Tool):
374
+ """Execute a notebook cell (requires jupyter kernel)"""
375
+
376
+ name = "NotebookRun"
377
+ description = (
378
+ "Execute a specific cell in a Jupyter notebook. "
379
+ "Requires nbconvert and a kernel to be available. "
380
+ "Returns the output of the execution."
381
+ )
382
+ category = "notebook"
383
+ permission = ToolPermission.ASK # Ask before executing code
384
+
385
+ parameters = [
386
+ ToolParameter(
387
+ name="notebook_path",
388
+ description="The absolute path to the Jupyter notebook file",
389
+ type="string",
390
+ required=True,
391
+ ),
392
+ ToolParameter(
393
+ name="cell_number",
394
+ description="The cell number to execute (0-indexed)",
395
+ type="integer",
396
+ required=True,
397
+ ),
398
+ ToolParameter(
399
+ name="timeout",
400
+ description="Execution timeout in seconds (default: 60)",
401
+ type="integer",
402
+ required=False,
403
+ default=60,
404
+ ),
405
+ ]
406
+
407
+ def execute(
408
+ self,
409
+ notebook_path: str,
410
+ cell_number: int,
411
+ timeout: int = 60,
412
+ ) -> ToolResult:
413
+ """Execute notebook cell"""
414
+ try:
415
+ import nbformat
416
+ from nbconvert.preprocessors import ExecutePreprocessor
417
+ except ImportError:
418
+ return ToolResult(
419
+ success=False,
420
+ output="",
421
+ error="nbconvert not installed. Install with: pip install nbconvert",
422
+ target=notebook_path,
423
+ )
424
+
425
+ path = Path(notebook_path).expanduser()
426
+
427
+ if not path.exists():
428
+ return ToolResult(
429
+ success=False,
430
+ output="",
431
+ error=f"Notebook not found: {notebook_path}",
432
+ target=notebook_path,
433
+ )
434
+
435
+ try:
436
+ # Read notebook
437
+ with open(path, "r", encoding="utf-8") as f:
438
+ nb = nbformat.read(f, as_version=4)
439
+
440
+ cells = nb.get("cells", [])
441
+ if cell_number < 0 or cell_number >= len(cells):
442
+ return ToolResult(
443
+ success=False,
444
+ output="",
445
+ error=f"Cell number {cell_number} out of range",
446
+ target=notebook_path,
447
+ )
448
+
449
+ cell = cells[cell_number]
450
+ if cell.get("cell_type") != "code":
451
+ return ToolResult(
452
+ success=False,
453
+ output="",
454
+ error=f"Cell {cell_number} is not a code cell",
455
+ target=notebook_path,
456
+ )
457
+
458
+ # Create a mini-notebook with just this cell
459
+ mini_nb = nbformat.v4.new_notebook()
460
+ mini_nb.cells = [cell]
461
+
462
+ # Execute
463
+ ep = ExecutePreprocessor(timeout=timeout, kernel_name='python3')
464
+ ep.preprocess(mini_nb, {'metadata': {'path': str(path.parent)}})
465
+
466
+ # Get outputs
467
+ executed_cell = mini_nb.cells[0]
468
+ outputs = executed_cell.get("outputs", [])
469
+
470
+ # Format outputs
471
+ output_parts = [f"Executed cell {cell_number}:"]
472
+ for out in outputs:
473
+ out_type = out.get("output_type", "")
474
+ if out_type == "stream":
475
+ text = "".join(out.get("text", []))
476
+ output_parts.append(f"[stream] {text}")
477
+ elif out_type == "execute_result":
478
+ data = out.get("data", {})
479
+ if "text/plain" in data:
480
+ text = "".join(data["text/plain"])
481
+ output_parts.append(f"[result] {text}")
482
+ elif out_type == "error":
483
+ ename = out.get("ename", "Error")
484
+ evalue = out.get("evalue", "")
485
+ output_parts.append(f"[error] {ename}: {evalue}")
486
+
487
+ # Update the original notebook's cell
488
+ cells[cell_number] = executed_cell
489
+ with open(path, "w", encoding="utf-8") as f:
490
+ nbformat.write(nb, f)
491
+
492
+ return ToolResult(
493
+ success=True,
494
+ output="\n".join(output_parts),
495
+ target=f"{notebook_path}:cell_{cell_number}",
496
+ data={
497
+ "cell_number": cell_number,
498
+ "execution_count": executed_cell.get("execution_count"),
499
+ },
500
+ )
501
+
502
+ except Exception as e:
503
+ return ToolResult(
504
+ success=False,
505
+ output="",
506
+ error=f"Error executing cell: {e}",
507
+ target=notebook_path,
508
+ )
509
+
510
+
511
+ # Register tools
512
+ def register_notebook_tools(registry):
513
+ """Register all notebook tools with a registry"""
514
+ registry.register_class(NotebookReadTool)
515
+ registry.register_class(NotebookEditTool)
516
+ registry.register_class(NotebookRunTool)