diff --git a/README.md b/README.md index c2d6af5..7b1bc99 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

-# Connpy (v6.1.0) +# Connpy (v6.1.1) [![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/) diff --git a/connpy/__init__.py b/connpy/__init__.py index d520eee..61827e8 100644 --- a/connpy/__init__.py +++ b/connpy/__init__.py @@ -5,7 +5,7 @@

-# Connpy (v6.1.0) +# Connpy (v6.1.1) [![](https://img.shields.io/pypi/v/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square)](https://pypi.org/pypi/connpy/) [![](https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400)](https://pypi.org/pypi/connpy/) diff --git a/connpy/_version.py b/connpy/_version.py index 7856d12..7f8a859 100644 --- a/connpy/_version.py +++ b/connpy/_version.py @@ -1 +1 @@ -__version__ = "6.1.0" +__version__ = "6.1.1" diff --git a/connpy/ai.py b/connpy/ai.py index a0b3f4e..5ba7ae1 100755 --- a/connpy/ai.py +++ b/connpy/ai.py @@ -33,7 +33,6 @@ def stream_chunk_builder(*args, **kwargs): return _stream_chunk_builder(*args, **kwargs) from .hooks import ClassHook, MethodHook from . import printer -from rich.markdown import Markdown from rich.panel import Panel from rich.text import Text from rich.console import Group @@ -1183,6 +1182,7 @@ class ai: if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(resp_msg.content), title=f"[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]", border_style="architect" if current_brain == "architect" else "engineer")) if status: try: status.start() @@ -1230,6 +1230,7 @@ class ai: if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect")) if status: try: status.start() @@ -1872,6 +1873,7 @@ class PlaybookBuilderAgent: chunk_callback(resp_msg.content) elif not resp_msg.tool_calls: # In direct non-streaming output, print markdown + from rich.markdown import Markdown self.console.print(Markdown(resp_msg.content)) if not resp_msg.tool_calls: diff --git a/connpy/cli/ai_handler.py b/connpy/cli/ai_handler.py index fabc377..6e7e149 100644 --- a/connpy/cli/ai_handler.py +++ b/connpy/cli/ai_handler.py @@ -1,6 +1,5 @@ import sys from rich.panel import Panel -from rich.markdown import Markdown from rich.rule import Rule from rich.prompt import Prompt @@ -102,6 +101,7 @@ class AIHandler: title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]" if not result.get("streamed"): + from rich.markdown import Markdown mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False)) if "usage" in result: @@ -121,6 +121,7 @@ class AIHandler: printer.info(f"Session '{session_id}' not found. Starting clean.") if not history: + from rich.markdown import Markdown mdprint(Rule(style="engineer")) mdprint(Markdown("**Networking Expert Agent**: Hi! I'm your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType 'exit' to quit.\n")) mdprint(Rule(style="engineer")) diff --git a/connpy/cli/node_handler.py b/connpy/cli/node_handler.py index 4555bac..0b1c9c8 100644 --- a/connpy/cli/node_handler.py +++ b/connpy/cli/node_handler.py @@ -1,6 +1,5 @@ import sys import yaml -from rich.markdown import Markdown from .. import printer from ..services.exceptions import ConnpyError, InvalidConfigurationError @@ -156,6 +155,7 @@ class NodeHandler: # Fast fail if parent folder does not exist self.app.services.nodes.validate_parent_folder(args.data) + from rich.markdown import Markdown printer.console.print(Markdown(get_instructions())) new_node_data = self.forms.questions_nodes(args.data, uniques) diff --git a/connpy/cli/terminal_ui.py b/connpy/cli/terminal_ui.py index 0515f00..57c12d2 100644 --- a/connpy/cli/terminal_ui.py +++ b/connpy/cli/terminal_ui.py @@ -11,7 +11,6 @@ from textwrap import dedent from rich.console import Console from rich.panel import Panel -from rich.markdown import Markdown from prompt_toolkit import PromptSession from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.filters import has_completions @@ -459,6 +458,7 @@ class CopilotInterface: # If no chunks were streamed but we have a guide, print it as a panel if first_chunk and result and result.get("guide"): + from rich.markdown import Markdown self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color)) commands = result.get("commands", []) diff --git a/connpy/configfile.py b/connpy/configfile.py index 461cdf8..fa9e605 100755 --- a/connpy/configfile.py +++ b/connpy/configfile.py @@ -6,8 +6,6 @@ import re import sys import yaml import shutil -from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_OAEP from pathlib import Path from copy import deepcopy from .hooks import MethodHook, ClassHook @@ -139,16 +137,39 @@ class configfile: self.connections = config["connections"] self.profiles = config["profiles"] + self._privatekey_obj = None + self._publickey_obj = None if not os.path.exists(self.key): self._createkey(self.key) - with open(self.key) as f: - self.privatekey = RSA.import_key(f.read()) - self.publickey = self.privatekey.publickey() # Self-heal text caches if they are missing if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile): self._generate_nodes_cache() + @property + def privatekey(self): + if getattr(self, '_privatekey_obj', None) is None: + from Crypto.PublicKey import RSA + if not os.path.exists(self.key): + self._createkey(self.key) + with open(self.key) as f: + self._privatekey_obj = RSA.import_key(f.read()) + return self._privatekey_obj + + @privatekey.setter + def privatekey(self, value): + self._privatekey_obj = value + + @property + def publickey(self): + if getattr(self, '_publickey_obj', None) is None: + self._publickey_obj = self.privatekey.publickey() + return self._publickey_obj + + @publickey.setter + def publickey(self, value): + self._publickey_obj = value + def get_effective_setting(self, key, default=None): """Get config setting with shared fallback for inheritable keys.""" @@ -297,6 +318,7 @@ class configfile: def _createkey(self, keyfile): #Create key file + from Crypto.PublicKey import RSA key = RSA.generate(2048) with open(keyfile,'wb') as f: f.write(key.export_key('PEM')) @@ -623,6 +645,8 @@ class configfile: if keyfile is None: keyfile = self.key with open(keyfile) as f: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_OAEP key = RSA.import_key(f.read()) f.close() publickey = key.publickey() diff --git a/connpy/core.py b/connpy/core.py index 5f30e50..5175a56 100755 --- a/connpy/core.py +++ b/connpy/core.py @@ -4,8 +4,6 @@ import os import re import pexpect import shlex -from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_OAEP import ast from time import sleep,time import datetime @@ -274,6 +272,8 @@ class node: if keyfile is None: keyfile = self.key if keyfile is not None: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_OAEP with open(keyfile) as f: key = RSA.import_key(f.read()) decryptor = PKCS1_OAEP.new(key) diff --git a/connpy/services/config_service.py b/connpy/services/config_service.py index 5f900bd..7e1fa28 100644 --- a/connpy/services/config_service.py +++ b/connpy/services/config_service.py @@ -2,8 +2,6 @@ import os import shutil import base64 from typing import Any, Dict -from Crypto.PublicKey import RSA -from Crypto.Cipher import PKCS1_OAEP from .base import BaseService from .exceptions import ConnpyError, InvalidConfigurationError, NodeNotFoundError diff --git a/docs/connpy/ai.html b/docs/connpy/ai.html index 7785f77..5913b4a 100644 --- a/docs/connpy/ai.html +++ b/docs/connpy/ai.html @@ -255,6 +255,7 @@ el.replaceWith(d); chunk_callback(resp_msg.content) elif not resp_msg.tool_calls: # In direct non-streaming output, print markdown + from rich.markdown import Markdown self.console.print(Markdown(resp_msg.content)) if not resp_msg.tool_calls: @@ -388,6 +389,7 @@ el.replaceWith(d); chunk_callback(resp_msg.content) elif not resp_msg.tool_calls: # In direct non-streaming output, print markdown + from rich.markdown import Markdown self.console.print(Markdown(resp_msg.content)) if not resp_msg.tool_calls: @@ -1621,6 +1623,7 @@ class ai: if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(resp_msg.content), title=f"[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]", border_style="architect" if current_brain == "architect" else "engineer")) if status: try: status.start() @@ -1668,6 +1671,7 @@ class ai: if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect")) if status: try: status.start() @@ -2579,6 +2583,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(resp_msg.content), title=f"[{current_brain}][bold]{label} Reasoning[/bold][/{current_brain}]", border_style="architect" if current_brain == "architect" else "engineer")) if status: try: status.start() @@ -2626,6 +2631,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa if status: try: status.stop() except: pass + from rich.markdown import Markdown self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect")) if status: try: status.start() diff --git a/docs/connpy/cli/ai_handler.html b/docs/connpy/cli/ai_handler.html index f915c21..8827e33 100644 --- a/docs/connpy/cli/ai_handler.html +++ b/docs/connpy/cli/ai_handler.html @@ -148,6 +148,7 @@ el.replaceWith(d); title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]" if not result.get("streamed"): + from rich.markdown import Markdown mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False)) if "usage" in result: @@ -167,6 +168,7 @@ el.replaceWith(d); printer.info(f"Session '{session_id}' not found. Starting clean.") if not history: + from rich.markdown import Markdown mdprint(Rule(style="engineer")) mdprint(Markdown("**Networking Expert Agent**: Hi! I'm your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType 'exit' to quit.\n")) mdprint(Rule(style="engineer")) @@ -573,6 +575,7 @@ el.replaceWith(d); printer.info(f"Session '{session_id}' not found. Starting clean.") if not history: + from rich.markdown import Markdown mdprint(Rule(style="engineer")) mdprint(Markdown("**Networking Expert Agent**: Hi! I'm your assistant. I can help you diagnose issues, run commands, and manage your nodes.\nType 'exit' to quit.\n")) mdprint(Rule(style="engineer")) @@ -626,6 +629,7 @@ el.replaceWith(d); title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]" if not result.get("streamed"): + from rich.markdown import Markdown mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False)) if "usage" in result: diff --git a/docs/connpy/cli/node_handler.html b/docs/connpy/cli/node_handler.html index 977bf19..1448004 100644 --- a/docs/connpy/cli/node_handler.html +++ b/docs/connpy/cli/node_handler.html @@ -204,6 +204,7 @@ el.replaceWith(d); # Fast fail if parent folder does not exist self.app.services.nodes.validate_parent_folder(args.data) + from rich.markdown import Markdown printer.console.print(Markdown(get_instructions())) new_node_data = self.forms.questions_nodes(args.data, uniques) @@ -374,6 +375,7 @@ def forms(self): # Fast fail if parent folder does not exist self.app.services.nodes.validate_parent_folder(args.data) + from rich.markdown import Markdown printer.console.print(Markdown(get_instructions())) new_node_data = self.forms.questions_nodes(args.data, uniques) diff --git a/docs/connpy/cli/terminal_ui.html b/docs/connpy/cli/terminal_ui.html index c53528f..c13d0a7 100644 --- a/docs/connpy/cli/terminal_ui.html +++ b/docs/connpy/cli/terminal_ui.html @@ -492,6 +492,7 @@ el.replaceWith(d); # If no chunks were streamed but we have a guide, print it as a panel if first_chunk and result and result.get("guide"): + from rich.markdown import Markdown self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color)) commands = result.get("commands", []) @@ -997,6 +998,7 @@ el.replaceWith(d); # If no chunks were streamed but we have a guide, print it as a panel if first_chunk and result and result.get("guide"): + from rich.markdown import Markdown self.console.print(Panel(Markdown(result["guide"]), title=f"[bold {persona_color}]{persona_title}[/bold {persona_color}]", border_style=persona_color)) commands = result.get("commands", []) diff --git a/docs/connpy/index.html b/docs/connpy/index.html index f974912..8d5b805 100644 --- a/docs/connpy/index.html +++ b/docs/connpy/index.html @@ -41,7 +41,7 @@ el.replaceWith(d);

App Logo

-

Connpy (v6.1.0)

+

Connpy (v6.1.1)

@@ -819,16 +819,39 @@ class configfile: self.connections = config["connections"] self.profiles = config["profiles"] + self._privatekey_obj = None + self._publickey_obj = None if not os.path.exists(self.key): self._createkey(self.key) - with open(self.key) as f: - self.privatekey = RSA.import_key(f.read()) - self.publickey = self.privatekey.publickey() # Self-heal text caches if they are missing if not os.path.exists(self.fzf_cachefile) or not os.path.exists(self.folders_cachefile) or not os.path.exists(self.profiles_cachefile): self._generate_nodes_cache() + @property + def privatekey(self): + if getattr(self, '_privatekey_obj', None) is None: + from Crypto.PublicKey import RSA + if not os.path.exists(self.key): + self._createkey(self.key) + with open(self.key) as f: + self._privatekey_obj = RSA.import_key(f.read()) + return self._privatekey_obj + + @privatekey.setter + def privatekey(self, value): + self._privatekey_obj = value + + @property + def publickey(self): + if getattr(self, '_publickey_obj', None) is None: + self._publickey_obj = self.privatekey.publickey() + return self._publickey_obj + + @publickey.setter + def publickey(self, value): + self._publickey_obj = value + def get_effective_setting(self, key, default=None): """Get config setting with shared fallback for inheritable keys.""" @@ -977,6 +1000,7 @@ class configfile: def _createkey(self, keyfile): #Create key file + from Crypto.PublicKey import RSA key = RSA.generate(2048) with open(keyfile,'wb') as f: f.write(key.export_key('PEM')) @@ -1303,6 +1327,8 @@ class configfile: if keyfile is None: keyfile = self.key with open(keyfile) as f: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_OAEP key = RSA.import_key(f.read()) f.close() publickey = key.publickey() @@ -1331,6 +1357,41 @@ class configfile: - publickey (obj): Object containing the public key to decrypt passwords. +

Instance variables

+
+
prop privatekey
+
+
+ +Expand source code + +
@property
+def privatekey(self):
+    if getattr(self, '_privatekey_obj', None) is None:
+        from Crypto.PublicKey import RSA
+        if not os.path.exists(self.key):
+            self._createkey(self.key)
+        with open(self.key) as f:
+            self._privatekey_obj = RSA.import_key(f.read())
+    return self._privatekey_obj
+
+
+
+
prop publickey
+
+
+ +Expand source code + +
@property
+def publickey(self):
+    if getattr(self, '_publickey_obj', None) is None:
+        self._publickey_obj = self.privatekey.publickey()
+    return self._publickey_obj
+
+
+
+

Methods

@@ -1363,6 +1424,8 @@ def encrypt(self, password, keyfile=None): if keyfile is None: keyfile = self.key with open(keyfile) as f: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_OAEP key = RSA.import_key(f.read()) f.close() publickey = key.publickey() @@ -1839,6 +1902,8 @@ class node: if keyfile is None: keyfile = self.key if keyfile is not None: + from Crypto.PublicKey import RSA + from Crypto.Cipher import PKCS1_OAEP with open(keyfile) as f: key = RSA.import_key(f.read()) decryptor = PKCS1_OAEP.new(key) @@ -4022,7 +4087,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,