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,623 @@
1
+ """
2
+ Docker Agent for NC1709
3
+ Handles Docker and Docker Compose operations
4
+ """
5
+ import subprocess
6
+ import json
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional, List
10
+ from dataclasses import dataclass
11
+
12
+ from ..base import (
13
+ Plugin, PluginMetadata, PluginCapability,
14
+ ActionResult
15
+ )
16
+
17
+
18
+ @dataclass
19
+ class ContainerInfo:
20
+ """Represents a Docker container"""
21
+ id: str
22
+ name: str
23
+ image: str
24
+ status: str
25
+ ports: str
26
+ created: str
27
+
28
+ @property
29
+ def is_running(self) -> bool:
30
+ return "Up" in self.status
31
+
32
+
33
+ @dataclass
34
+ class ImageInfo:
35
+ """Represents a Docker image"""
36
+ id: str
37
+ repository: str
38
+ tag: str
39
+ size: str
40
+ created: str
41
+
42
+
43
+ class DockerAgent(Plugin):
44
+ """
45
+ Docker operations agent.
46
+
47
+ Provides Docker and Docker Compose operations:
48
+ - Container management (list, start, stop, remove)
49
+ - Image management (list, pull, build, remove)
50
+ - Docker Compose operations
51
+ - Log viewing and inspection
52
+ """
53
+
54
+ METADATA = PluginMetadata(
55
+ name="docker",
56
+ version="1.0.0",
57
+ description="Docker container management",
58
+ author="NC1709 Team",
59
+ capabilities=[
60
+ PluginCapability.CONTAINER_MANAGEMENT,
61
+ PluginCapability.COMMAND_EXECUTION
62
+ ],
63
+ keywords=[
64
+ "docker", "container", "image", "compose", "build",
65
+ "pull", "push", "run", "stop", "start", "logs",
66
+ "dockerfile", "docker-compose", "volume", "network"
67
+ ],
68
+ config_schema={
69
+ "compose_file": {"type": "string", "default": "docker-compose.yml"},
70
+ "default_registry": {"type": "string", "default": ""},
71
+ "build_context": {"type": "string", "default": "."}
72
+ }
73
+ )
74
+
75
+ @property
76
+ def metadata(self) -> PluginMetadata:
77
+ return self.METADATA
78
+
79
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
80
+ super().__init__(config)
81
+ self._docker_available = False
82
+ self._compose_available = False
83
+
84
+ def initialize(self) -> bool:
85
+ """Initialize the Docker agent"""
86
+ # Check Docker
87
+ try:
88
+ result = subprocess.run(
89
+ ["docker", "--version"],
90
+ capture_output=True,
91
+ text=True
92
+ )
93
+ self._docker_available = result.returncode == 0
94
+ except FileNotFoundError:
95
+ self._error = "Docker is not installed"
96
+ return False
97
+
98
+ # Check Docker Compose
99
+ try:
100
+ result = subprocess.run(
101
+ ["docker", "compose", "version"],
102
+ capture_output=True,
103
+ text=True
104
+ )
105
+ self._compose_available = result.returncode == 0
106
+ except Exception:
107
+ # Try legacy docker-compose
108
+ try:
109
+ result = subprocess.run(
110
+ ["docker-compose", "--version"],
111
+ capture_output=True,
112
+ text=True
113
+ )
114
+ self._compose_available = result.returncode == 0
115
+ except Exception:
116
+ pass
117
+
118
+ return self._docker_available
119
+
120
+ def cleanup(self) -> None:
121
+ """Cleanup resources"""
122
+ pass
123
+
124
+ def _register_actions(self) -> None:
125
+ """Register Docker actions"""
126
+ # Container actions
127
+ self.register_action(
128
+ "ps",
129
+ self.list_containers,
130
+ "List containers",
131
+ parameters={"all": {"type": "boolean", "default": False}}
132
+ )
133
+
134
+ self.register_action(
135
+ "start",
136
+ self.start_container,
137
+ "Start a container",
138
+ parameters={"container": {"type": "string", "required": True}}
139
+ )
140
+
141
+ self.register_action(
142
+ "stop",
143
+ self.stop_container,
144
+ "Stop a container",
145
+ parameters={"container": {"type": "string", "required": True}},
146
+ requires_confirmation=True
147
+ )
148
+
149
+ self.register_action(
150
+ "remove",
151
+ self.remove_container,
152
+ "Remove a container",
153
+ parameters={
154
+ "container": {"type": "string", "required": True},
155
+ "force": {"type": "boolean", "default": False}
156
+ },
157
+ requires_confirmation=True,
158
+ dangerous=True
159
+ )
160
+
161
+ self.register_action(
162
+ "logs",
163
+ self.get_logs,
164
+ "View container logs",
165
+ parameters={
166
+ "container": {"type": "string", "required": True},
167
+ "tail": {"type": "integer", "default": 100},
168
+ "follow": {"type": "boolean", "default": False}
169
+ }
170
+ )
171
+
172
+ self.register_action(
173
+ "exec",
174
+ self.exec_in_container,
175
+ "Execute command in container",
176
+ parameters={
177
+ "container": {"type": "string", "required": True},
178
+ "command": {"type": "string", "required": True}
179
+ }
180
+ )
181
+
182
+ # Image actions
183
+ self.register_action(
184
+ "images",
185
+ self.list_images,
186
+ "List images"
187
+ )
188
+
189
+ self.register_action(
190
+ "pull",
191
+ self.pull_image,
192
+ "Pull an image",
193
+ parameters={"image": {"type": "string", "required": True}}
194
+ )
195
+
196
+ self.register_action(
197
+ "build",
198
+ self.build_image,
199
+ "Build an image",
200
+ parameters={
201
+ "tag": {"type": "string", "required": True},
202
+ "dockerfile": {"type": "string", "default": "Dockerfile"},
203
+ "context": {"type": "string", "default": "."}
204
+ }
205
+ )
206
+
207
+ self.register_action(
208
+ "rmi",
209
+ self.remove_image,
210
+ "Remove an image",
211
+ parameters={
212
+ "image": {"type": "string", "required": True},
213
+ "force": {"type": "boolean", "default": False}
214
+ },
215
+ requires_confirmation=True,
216
+ dangerous=True
217
+ )
218
+
219
+ # Compose actions
220
+ self.register_action(
221
+ "compose_up",
222
+ self.compose_up,
223
+ "Start services with docker-compose",
224
+ parameters={
225
+ "detach": {"type": "boolean", "default": True},
226
+ "build": {"type": "boolean", "default": False},
227
+ "services": {"type": "array", "optional": True}
228
+ }
229
+ )
230
+
231
+ self.register_action(
232
+ "compose_down",
233
+ self.compose_down,
234
+ "Stop services with docker-compose",
235
+ parameters={
236
+ "volumes": {"type": "boolean", "default": False},
237
+ "remove_orphans": {"type": "boolean", "default": False}
238
+ },
239
+ requires_confirmation=True
240
+ )
241
+
242
+ self.register_action(
243
+ "compose_ps",
244
+ self.compose_ps,
245
+ "List compose services"
246
+ )
247
+
248
+ # Utility actions
249
+ self.register_action(
250
+ "prune",
251
+ self.prune,
252
+ "Remove unused resources",
253
+ parameters={
254
+ "type": {"type": "string", "enum": ["containers", "images", "volumes", "all"]}
255
+ },
256
+ requires_confirmation=True,
257
+ dangerous=True
258
+ )
259
+
260
+ def _run_docker(self, *args, timeout: int = 60) -> subprocess.CompletedProcess:
261
+ """Run a docker command"""
262
+ cmd = ["docker"] + list(args)
263
+ return subprocess.run(
264
+ cmd,
265
+ capture_output=True,
266
+ text=True,
267
+ timeout=timeout
268
+ )
269
+
270
+ def _run_compose(self, *args, timeout: int = 120) -> subprocess.CompletedProcess:
271
+ """Run a docker-compose command"""
272
+ compose_file = self._config.get("compose_file", "docker-compose.yml")
273
+
274
+ # Try new syntax first
275
+ cmd = ["docker", "compose", "-f", compose_file] + list(args)
276
+ try:
277
+ result = subprocess.run(
278
+ cmd,
279
+ capture_output=True,
280
+ text=True,
281
+ timeout=timeout
282
+ )
283
+ if result.returncode == 0 or "unknown docker command" not in result.stderr.lower():
284
+ return result
285
+ except Exception:
286
+ pass
287
+
288
+ # Fall back to docker-compose
289
+ cmd = ["docker-compose", "-f", compose_file] + list(args)
290
+ return subprocess.run(
291
+ cmd,
292
+ capture_output=True,
293
+ text=True,
294
+ timeout=timeout
295
+ )
296
+
297
+ # Container operations
298
+
299
+ def list_containers(self, all: bool = False) -> ActionResult:
300
+ """List Docker containers
301
+
302
+ Args:
303
+ all: Include stopped containers
304
+
305
+ Returns:
306
+ ActionResult with container list
307
+ """
308
+ args = ["ps", "--format", "{{.ID}}|{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}|{{.CreatedAt}}"]
309
+
310
+ if all:
311
+ args.append("-a")
312
+
313
+ result = self._run_docker(*args)
314
+
315
+ if result.returncode != 0:
316
+ return ActionResult.fail(result.stderr)
317
+
318
+ containers = []
319
+ for line in result.stdout.strip().split("\n"):
320
+ if not line:
321
+ continue
322
+ parts = line.split("|")
323
+ if len(parts) >= 6:
324
+ containers.append(ContainerInfo(
325
+ id=parts[0],
326
+ name=parts[1],
327
+ image=parts[2],
328
+ status=parts[3],
329
+ ports=parts[4],
330
+ created=parts[5]
331
+ ))
332
+
333
+ running = sum(1 for c in containers if c.is_running)
334
+
335
+ return ActionResult.ok(
336
+ message=f"{len(containers)} containers ({running} running)",
337
+ data=containers
338
+ )
339
+
340
+ def start_container(self, container: str) -> ActionResult:
341
+ """Start a container"""
342
+ result = self._run_docker("start", container)
343
+
344
+ if result.returncode != 0:
345
+ return ActionResult.fail(result.stderr)
346
+
347
+ return ActionResult.ok(f"Started container: {container}")
348
+
349
+ def stop_container(self, container: str) -> ActionResult:
350
+ """Stop a container"""
351
+ result = self._run_docker("stop", container)
352
+
353
+ if result.returncode != 0:
354
+ return ActionResult.fail(result.stderr)
355
+
356
+ return ActionResult.ok(f"Stopped container: {container}")
357
+
358
+ def remove_container(self, container: str, force: bool = False) -> ActionResult:
359
+ """Remove a container"""
360
+ args = ["rm"]
361
+ if force:
362
+ args.append("-f")
363
+ args.append(container)
364
+
365
+ result = self._run_docker(*args)
366
+
367
+ if result.returncode != 0:
368
+ return ActionResult.fail(result.stderr)
369
+
370
+ return ActionResult.ok(f"Removed container: {container}")
371
+
372
+ def get_logs(
373
+ self,
374
+ container: str,
375
+ tail: int = 100,
376
+ follow: bool = False
377
+ ) -> ActionResult:
378
+ """Get container logs"""
379
+ args = ["logs", f"--tail={tail}"]
380
+
381
+ if follow:
382
+ # For follow, we'd need streaming - just get latest
383
+ pass
384
+
385
+ args.append(container)
386
+
387
+ result = self._run_docker(*args)
388
+
389
+ if result.returncode != 0:
390
+ return ActionResult.fail(result.stderr)
391
+
392
+ # Combine stdout and stderr (logs can go to either)
393
+ logs = result.stdout + result.stderr
394
+
395
+ return ActionResult.ok(
396
+ message=f"Logs for {container} (last {tail} lines)",
397
+ data=logs
398
+ )
399
+
400
+ def exec_in_container(self, container: str, command: str) -> ActionResult:
401
+ """Execute command in container"""
402
+ args = ["exec", container] + command.split()
403
+
404
+ result = self._run_docker(*args)
405
+
406
+ return ActionResult.ok(
407
+ message=f"Executed in {container}",
408
+ data={"stdout": result.stdout, "stderr": result.stderr, "exit_code": result.returncode}
409
+ )
410
+
411
+ # Image operations
412
+
413
+ def list_images(self) -> ActionResult:
414
+ """List Docker images"""
415
+ result = self._run_docker(
416
+ "images",
417
+ "--format", "{{.ID}}|{{.Repository}}|{{.Tag}}|{{.Size}}|{{.CreatedAt}}"
418
+ )
419
+
420
+ if result.returncode != 0:
421
+ return ActionResult.fail(result.stderr)
422
+
423
+ images = []
424
+ for line in result.stdout.strip().split("\n"):
425
+ if not line:
426
+ continue
427
+ parts = line.split("|")
428
+ if len(parts) >= 5:
429
+ images.append(ImageInfo(
430
+ id=parts[0],
431
+ repository=parts[1],
432
+ tag=parts[2],
433
+ size=parts[3],
434
+ created=parts[4]
435
+ ))
436
+
437
+ return ActionResult.ok(
438
+ message=f"{len(images)} images",
439
+ data=images
440
+ )
441
+
442
+ def pull_image(self, image: str) -> ActionResult:
443
+ """Pull an image"""
444
+ result = self._run_docker("pull", image, timeout=300)
445
+
446
+ if result.returncode != 0:
447
+ return ActionResult.fail(result.stderr)
448
+
449
+ return ActionResult.ok(
450
+ message=f"Pulled image: {image}",
451
+ data=result.stdout
452
+ )
453
+
454
+ def build_image(
455
+ self,
456
+ tag: str,
457
+ dockerfile: str = "Dockerfile",
458
+ context: str = "."
459
+ ) -> ActionResult:
460
+ """Build a Docker image"""
461
+ args = ["build", "-t", tag, "-f", dockerfile, context]
462
+
463
+ result = self._run_docker(*args, timeout=600)
464
+
465
+ if result.returncode != 0:
466
+ return ActionResult.fail(result.stderr)
467
+
468
+ return ActionResult.ok(
469
+ message=f"Built image: {tag}",
470
+ data=result.stdout
471
+ )
472
+
473
+ def remove_image(self, image: str, force: bool = False) -> ActionResult:
474
+ """Remove an image"""
475
+ args = ["rmi"]
476
+ if force:
477
+ args.append("-f")
478
+ args.append(image)
479
+
480
+ result = self._run_docker(*args)
481
+
482
+ if result.returncode != 0:
483
+ return ActionResult.fail(result.stderr)
484
+
485
+ return ActionResult.ok(f"Removed image: {image}")
486
+
487
+ # Docker Compose operations
488
+
489
+ def compose_up(
490
+ self,
491
+ detach: bool = True,
492
+ build: bool = False,
493
+ services: Optional[List[str]] = None
494
+ ) -> ActionResult:
495
+ """Start docker-compose services"""
496
+ if not self._compose_available:
497
+ return ActionResult.fail("Docker Compose not available")
498
+
499
+ args = ["up"]
500
+
501
+ if detach:
502
+ args.append("-d")
503
+ if build:
504
+ args.append("--build")
505
+
506
+ if services:
507
+ args.extend(services)
508
+
509
+ result = self._run_compose(*args, timeout=300)
510
+
511
+ if result.returncode != 0:
512
+ return ActionResult.fail(result.stderr)
513
+
514
+ return ActionResult.ok(
515
+ message="Services started",
516
+ data=result.stdout
517
+ )
518
+
519
+ def compose_down(
520
+ self,
521
+ volumes: bool = False,
522
+ remove_orphans: bool = False
523
+ ) -> ActionResult:
524
+ """Stop docker-compose services"""
525
+ if not self._compose_available:
526
+ return ActionResult.fail("Docker Compose not available")
527
+
528
+ args = ["down"]
529
+
530
+ if volumes:
531
+ args.append("-v")
532
+ if remove_orphans:
533
+ args.append("--remove-orphans")
534
+
535
+ result = self._run_compose(*args)
536
+
537
+ if result.returncode != 0:
538
+ return ActionResult.fail(result.stderr)
539
+
540
+ return ActionResult.ok(
541
+ message="Services stopped",
542
+ data=result.stdout
543
+ )
544
+
545
+ def compose_ps(self) -> ActionResult:
546
+ """List docker-compose services"""
547
+ if not self._compose_available:
548
+ return ActionResult.fail("Docker Compose not available")
549
+
550
+ result = self._run_compose("ps")
551
+
552
+ if result.returncode != 0:
553
+ return ActionResult.fail(result.stderr)
554
+
555
+ return ActionResult.ok(
556
+ message="Compose services",
557
+ data=result.stdout
558
+ )
559
+
560
+ # Utility operations
561
+
562
+ def prune(self, type: str = "containers") -> ActionResult:
563
+ """Remove unused Docker resources"""
564
+ if type == "containers":
565
+ result = self._run_docker("container", "prune", "-f")
566
+ elif type == "images":
567
+ result = self._run_docker("image", "prune", "-f")
568
+ elif type == "volumes":
569
+ result = self._run_docker("volume", "prune", "-f")
570
+ elif type == "all":
571
+ result = self._run_docker("system", "prune", "-f")
572
+ else:
573
+ return ActionResult.fail(f"Unknown prune type: {type}")
574
+
575
+ if result.returncode != 0:
576
+ return ActionResult.fail(result.stderr)
577
+
578
+ return ActionResult.ok(
579
+ message=f"Pruned {type}",
580
+ data=result.stdout
581
+ )
582
+
583
+ def can_handle(self, request: str) -> float:
584
+ """Check if request is Docker-related"""
585
+ request_lower = request.lower()
586
+
587
+ # High confidence
588
+ high_conf = ["docker", "container", "compose", "dockerfile"]
589
+ for kw in high_conf:
590
+ if kw in request_lower:
591
+ return 0.9
592
+
593
+ # Medium confidence
594
+ med_conf = ["image", "build", "deploy", "service"]
595
+ for kw in med_conf:
596
+ if kw in request_lower:
597
+ return 0.5
598
+
599
+ return super().can_handle(request)
600
+
601
+ def handle_request(self, request: str, **kwargs) -> Optional[ActionResult]:
602
+ """Handle a natural language request"""
603
+ request_lower = request.lower()
604
+
605
+ # Container list
606
+ if any(kw in request_lower for kw in ["list containers", "ps", "running containers"]):
607
+ all_containers = "all" in request_lower or "stopped" in request_lower
608
+ return self.list_containers(all=all_containers)
609
+
610
+ # Images
611
+ if "list images" in request_lower or "show images" in request_lower:
612
+ return self.list_images()
613
+
614
+ # Compose
615
+ if "compose" in request_lower:
616
+ if "up" in request_lower or "start" in request_lower:
617
+ return self.compose_up()
618
+ if "down" in request_lower or "stop" in request_lower:
619
+ return self.compose_down()
620
+ if "ps" in request_lower or "status" in request_lower:
621
+ return self.compose_ps()
622
+
623
+ return None