perf(cli,core): lazy load Crypto and rich.markdown modules to boost startup latency and bump to v6.1.1
- Implement lazy `@property` getters for `configfile.privatekey` and `configfile.publickey` to defer RSA key loading and pycryptodome imports until explicitly accessed. - Move top-level `Crypto` imports inside local methods in `configfile.py`, `core.py`, and `config_service.py`. - Remove top-level `from rich.markdown import Markdown` across CLI handlers (`node_handler.py`, `ai_handler.py`, `terminal_ui.py`) and `ai.py`, deferring parser load to active rendering calls. - Reduce CLI startup load latency from ~172 ms to ~49 ms (>3.5x speedup / ~71% latency reduction). - Regenerate HTML documentation in `docs/` via `pdoc`. - Bump version to v6.1.1.
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
# Connpy (v6.1.0)
|
# Connpy (v6.1.1)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
# Connpy (v6.1.0)
|
# Connpy (v6.1.1)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
[](https://pypi.org/pypi/connpy/)
|
[](https://pypi.org/pypi/connpy/)
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
__version__ = "6.1.0"
|
__version__ = "6.1.1"
|
||||||
|
|||||||
+3
-1
@@ -33,7 +33,6 @@ def stream_chunk_builder(*args, **kwargs):
|
|||||||
return _stream_chunk_builder(*args, **kwargs)
|
return _stream_chunk_builder(*args, **kwargs)
|
||||||
from .hooks import ClassHook, MethodHook
|
from .hooks import ClassHook, MethodHook
|
||||||
from . import printer
|
from . import printer
|
||||||
from rich.markdown import Markdown
|
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from rich.console import Group
|
from rich.console import Group
|
||||||
@@ -1183,6 +1182,7 @@ class ai:
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
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"))
|
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:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
@@ -1230,6 +1230,7 @@ class ai:
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
except: pass
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
||||||
if status:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
@@ -1872,6 +1873,7 @@ class PlaybookBuilderAgent:
|
|||||||
chunk_callback(resp_msg.content)
|
chunk_callback(resp_msg.content)
|
||||||
elif not resp_msg.tool_calls:
|
elif not resp_msg.tool_calls:
|
||||||
# In direct non-streaming output, print markdown
|
# In direct non-streaming output, print markdown
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Markdown(resp_msg.content))
|
self.console.print(Markdown(resp_msg.content))
|
||||||
|
|
||||||
if not resp_msg.tool_calls:
|
if not resp_msg.tool_calls:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.markdown import Markdown
|
|
||||||
from rich.rule import Rule
|
from rich.rule import Rule
|
||||||
from rich.prompt import Prompt
|
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]"
|
title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]"
|
||||||
|
|
||||||
if not result.get("streamed"):
|
if not result.get("streamed"):
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
||||||
|
|
||||||
if "usage" in result:
|
if "usage" in result:
|
||||||
@@ -121,6 +121,7 @@ class AIHandler:
|
|||||||
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
||||||
|
|
||||||
if not history:
|
if not history:
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Rule(style="engineer"))
|
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(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"))
|
mdprint(Rule(style="engineer"))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
from rich.markdown import Markdown
|
|
||||||
|
|
||||||
from .. import printer
|
from .. import printer
|
||||||
from ..services.exceptions import ConnpyError, InvalidConfigurationError
|
from ..services.exceptions import ConnpyError, InvalidConfigurationError
|
||||||
@@ -156,6 +155,7 @@ class NodeHandler:
|
|||||||
# Fast fail if parent folder does not exist
|
# Fast fail if parent folder does not exist
|
||||||
self.app.services.nodes.validate_parent_folder(args.data)
|
self.app.services.nodes.validate_parent_folder(args.data)
|
||||||
|
|
||||||
|
from rich.markdown import Markdown
|
||||||
printer.console.print(Markdown(get_instructions()))
|
printer.console.print(Markdown(get_instructions()))
|
||||||
|
|
||||||
new_node_data = self.forms.questions_nodes(args.data, uniques)
|
new_node_data = self.forms.questions_nodes(args.data, uniques)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from textwrap import dedent
|
|||||||
|
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.markdown import Markdown
|
|
||||||
from prompt_toolkit import PromptSession
|
from prompt_toolkit import PromptSession
|
||||||
from prompt_toolkit.key_binding import KeyBindings
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
from prompt_toolkit.filters import has_completions
|
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 no chunks were streamed but we have a guide, print it as a panel
|
||||||
if first_chunk and result and result.get("guide"):
|
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))
|
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", [])
|
commands = result.get("commands", [])
|
||||||
|
|||||||
+29
-5
@@ -6,8 +6,6 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import shutil
|
import shutil
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Cipher import PKCS1_OAEP
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from .hooks import MethodHook, ClassHook
|
from .hooks import MethodHook, ClassHook
|
||||||
@@ -139,16 +137,39 @@ class configfile:
|
|||||||
self.connections = config["connections"]
|
self.connections = config["connections"]
|
||||||
self.profiles = config["profiles"]
|
self.profiles = config["profiles"]
|
||||||
|
|
||||||
|
self._privatekey_obj = None
|
||||||
|
self._publickey_obj = None
|
||||||
if not os.path.exists(self.key):
|
if not os.path.exists(self.key):
|
||||||
self._createkey(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
|
# 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):
|
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()
|
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):
|
def get_effective_setting(self, key, default=None):
|
||||||
"""Get config setting with shared fallback for inheritable keys."""
|
"""Get config setting with shared fallback for inheritable keys."""
|
||||||
@@ -297,6 +318,7 @@ class configfile:
|
|||||||
|
|
||||||
def _createkey(self, keyfile):
|
def _createkey(self, keyfile):
|
||||||
#Create key file
|
#Create key file
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
with open(keyfile,'wb') as f:
|
with open(keyfile,'wb') as f:
|
||||||
f.write(key.export_key('PEM'))
|
f.write(key.export_key('PEM'))
|
||||||
@@ -623,6 +645,8 @@ class configfile:
|
|||||||
if keyfile is None:
|
if keyfile is None:
|
||||||
keyfile = self.key
|
keyfile = self.key
|
||||||
with open(keyfile) as f:
|
with open(keyfile) as f:
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
key = RSA.import_key(f.read())
|
key = RSA.import_key(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
publickey = key.publickey()
|
publickey = key.publickey()
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import pexpect
|
import pexpect
|
||||||
import shlex
|
import shlex
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Cipher import PKCS1_OAEP
|
|
||||||
import ast
|
import ast
|
||||||
from time import sleep,time
|
from time import sleep,time
|
||||||
import datetime
|
import datetime
|
||||||
@@ -274,6 +272,8 @@ class node:
|
|||||||
if keyfile is None:
|
if keyfile is None:
|
||||||
keyfile = self.key
|
keyfile = self.key
|
||||||
if keyfile is not None:
|
if keyfile is not None:
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
with open(keyfile) as f:
|
with open(keyfile) as f:
|
||||||
key = RSA.import_key(f.read())
|
key = RSA.import_key(f.read())
|
||||||
decryptor = PKCS1_OAEP.new(key)
|
decryptor = PKCS1_OAEP.new(key)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import base64
|
import base64
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Cipher import PKCS1_OAEP
|
|
||||||
from .base import BaseService
|
from .base import BaseService
|
||||||
from .exceptions import ConnpyError, InvalidConfigurationError, NodeNotFoundError
|
from .exceptions import ConnpyError, InvalidConfigurationError, NodeNotFoundError
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ el.replaceWith(d);
|
|||||||
chunk_callback(resp_msg.content)
|
chunk_callback(resp_msg.content)
|
||||||
elif not resp_msg.tool_calls:
|
elif not resp_msg.tool_calls:
|
||||||
# In direct non-streaming output, print markdown
|
# In direct non-streaming output, print markdown
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Markdown(resp_msg.content))
|
self.console.print(Markdown(resp_msg.content))
|
||||||
|
|
||||||
if not resp_msg.tool_calls:
|
if not resp_msg.tool_calls:
|
||||||
@@ -388,6 +389,7 @@ el.replaceWith(d);
|
|||||||
chunk_callback(resp_msg.content)
|
chunk_callback(resp_msg.content)
|
||||||
elif not resp_msg.tool_calls:
|
elif not resp_msg.tool_calls:
|
||||||
# In direct non-streaming output, print markdown
|
# In direct non-streaming output, print markdown
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Markdown(resp_msg.content))
|
self.console.print(Markdown(resp_msg.content))
|
||||||
|
|
||||||
if not resp_msg.tool_calls:
|
if not resp_msg.tool_calls:
|
||||||
@@ -1621,6 +1623,7 @@ class ai:
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
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"))
|
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:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
@@ -1668,6 +1671,7 @@ class ai:
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
except: pass
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
||||||
if status:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
@@ -2579,6 +2583,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
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"))
|
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:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
@@ -2626,6 +2631,7 @@ def ask(self, user_input, dryrun=False, chat_history=None, status=None, debug=Fa
|
|||||||
if status:
|
if status:
|
||||||
try: status.stop()
|
try: status.stop()
|
||||||
except: pass
|
except: pass
|
||||||
|
from rich.markdown import Markdown
|
||||||
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
self.console.print(Panel(Markdown(obs), title="[architect]Architect Consultation[/architect]", border_style="architect"))
|
||||||
if status:
|
if status:
|
||||||
try: status.start()
|
try: status.start()
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ el.replaceWith(d);
|
|||||||
title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]"
|
title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]"
|
||||||
|
|
||||||
if not result.get("streamed"):
|
if not result.get("streamed"):
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
||||||
|
|
||||||
if "usage" in result:
|
if "usage" in result:
|
||||||
@@ -167,6 +168,7 @@ el.replaceWith(d);
|
|||||||
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
||||||
|
|
||||||
if not history:
|
if not history:
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Rule(style="engineer"))
|
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(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"))
|
mdprint(Rule(style="engineer"))
|
||||||
@@ -573,6 +575,7 @@ el.replaceWith(d);
|
|||||||
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
printer.info(f"Session '{session_id}' not found. Starting clean.")
|
||||||
|
|
||||||
if not history:
|
if not history:
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Rule(style="engineer"))
|
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(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"))
|
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]"
|
title = "[architect][bold]Network Architect[/bold][/architect]" if responder == "architect" else "[engineer][bold]Network Engineer[/bold][/engineer]"
|
||||||
|
|
||||||
if not result.get("streamed"):
|
if not result.get("streamed"):
|
||||||
|
from rich.markdown import Markdown
|
||||||
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
mdprint(Panel(Markdown(result["response"]), title=title, border_style=border, expand=False))
|
||||||
|
|
||||||
if "usage" in result:
|
if "usage" in result:
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ el.replaceWith(d);
|
|||||||
# Fast fail if parent folder does not exist
|
# Fast fail if parent folder does not exist
|
||||||
self.app.services.nodes.validate_parent_folder(args.data)
|
self.app.services.nodes.validate_parent_folder(args.data)
|
||||||
|
|
||||||
|
from rich.markdown import Markdown
|
||||||
printer.console.print(Markdown(get_instructions()))
|
printer.console.print(Markdown(get_instructions()))
|
||||||
|
|
||||||
new_node_data = self.forms.questions_nodes(args.data, uniques)
|
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
|
# Fast fail if parent folder does not exist
|
||||||
self.app.services.nodes.validate_parent_folder(args.data)
|
self.app.services.nodes.validate_parent_folder(args.data)
|
||||||
|
|
||||||
|
from rich.markdown import Markdown
|
||||||
printer.console.print(Markdown(get_instructions()))
|
printer.console.print(Markdown(get_instructions()))
|
||||||
|
|
||||||
new_node_data = self.forms.questions_nodes(args.data, uniques)
|
new_node_data = self.forms.questions_nodes(args.data, uniques)
|
||||||
|
|||||||
@@ -492,6 +492,7 @@ el.replaceWith(d);
|
|||||||
|
|
||||||
# If no chunks were streamed but we have a guide, print it as a panel
|
# If no chunks were streamed but we have a guide, print it as a panel
|
||||||
if first_chunk and result and result.get("guide"):
|
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))
|
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", [])
|
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 no chunks were streamed but we have a guide, print it as a panel
|
||||||
if first_chunk and result and result.get("guide"):
|
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))
|
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", [])
|
commands = result.get("commands", [])
|
||||||
|
|||||||
+72
-5
@@ -41,7 +41,7 @@ el.replaceWith(d);
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
|
<img src="https://nginx.gederico.dynu.net/images/CONNPY-resized.png" alt="App Logo">
|
||||||
</p>
|
</p>
|
||||||
<h1 id="connpy-v610">Connpy (v6.1.0)</h1>
|
<h1 id="connpy-v611">Connpy (v6.1.1)</h1>
|
||||||
<p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a>
|
<p><a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/v/connpy.svg?style=flat-square"></a>
|
||||||
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a>
|
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/pyversions/connpy.svg?style=flat-square"></a>
|
||||||
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400"></a>
|
<a href="https://pypi.org/pypi/connpy/"><img alt="" src="https://img.shields.io/pypi/dm/connpy.svg?style=flat-square&cacheSeconds=86400"></a>
|
||||||
@@ -819,16 +819,39 @@ class configfile:
|
|||||||
self.connections = config["connections"]
|
self.connections = config["connections"]
|
||||||
self.profiles = config["profiles"]
|
self.profiles = config["profiles"]
|
||||||
|
|
||||||
|
self._privatekey_obj = None
|
||||||
|
self._publickey_obj = None
|
||||||
if not os.path.exists(self.key):
|
if not os.path.exists(self.key):
|
||||||
self._createkey(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
|
# 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):
|
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()
|
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):
|
def get_effective_setting(self, key, default=None):
|
||||||
"""Get config setting with shared fallback for inheritable keys."""
|
"""Get config setting with shared fallback for inheritable keys."""
|
||||||
@@ -977,6 +1000,7 @@ class configfile:
|
|||||||
|
|
||||||
def _createkey(self, keyfile):
|
def _createkey(self, keyfile):
|
||||||
#Create key file
|
#Create key file
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
key = RSA.generate(2048)
|
key = RSA.generate(2048)
|
||||||
with open(keyfile,'wb') as f:
|
with open(keyfile,'wb') as f:
|
||||||
f.write(key.export_key('PEM'))
|
f.write(key.export_key('PEM'))
|
||||||
@@ -1303,6 +1327,8 @@ class configfile:
|
|||||||
if keyfile is None:
|
if keyfile is None:
|
||||||
keyfile = self.key
|
keyfile = self.key
|
||||||
with open(keyfile) as f:
|
with open(keyfile) as f:
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
key = RSA.import_key(f.read())
|
key = RSA.import_key(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
publickey = key.publickey()
|
publickey = key.publickey()
|
||||||
@@ -1331,6 +1357,41 @@ class configfile:
|
|||||||
- publickey (obj): Object containing the public key to decrypt
|
- publickey (obj): Object containing the public key to decrypt
|
||||||
passwords.
|
passwords.
|
||||||
</code></pre></div>
|
</code></pre></div>
|
||||||
|
<h3>Instance variables</h3>
|
||||||
|
<dl>
|
||||||
|
<dt id="connpy.configfile.privatekey"><code class="name">prop <span class="ident">privatekey</span></code></dt>
|
||||||
|
<dd>
|
||||||
|
<details class="source">
|
||||||
|
<summary>
|
||||||
|
<span>Expand source code</span>
|
||||||
|
</summary>
|
||||||
|
<pre><code class="python">@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</code></pre>
|
||||||
|
</details>
|
||||||
|
<div class="desc"></div>
|
||||||
|
</dd>
|
||||||
|
<dt id="connpy.configfile.publickey"><code class="name">prop <span class="ident">publickey</span></code></dt>
|
||||||
|
<dd>
|
||||||
|
<details class="source">
|
||||||
|
<summary>
|
||||||
|
<span>Expand source code</span>
|
||||||
|
</summary>
|
||||||
|
<pre><code class="python">@property
|
||||||
|
def publickey(self):
|
||||||
|
if getattr(self, '_publickey_obj', None) is None:
|
||||||
|
self._publickey_obj = self.privatekey.publickey()
|
||||||
|
return self._publickey_obj</code></pre>
|
||||||
|
</details>
|
||||||
|
<div class="desc"></div>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
<h3>Methods</h3>
|
<h3>Methods</h3>
|
||||||
<dl>
|
<dl>
|
||||||
<dt id="connpy.configfile.encrypt"><code class="name flex">
|
<dt id="connpy.configfile.encrypt"><code class="name flex">
|
||||||
@@ -1363,6 +1424,8 @@ def encrypt(self, password, keyfile=None):
|
|||||||
if keyfile is None:
|
if keyfile is None:
|
||||||
keyfile = self.key
|
keyfile = self.key
|
||||||
with open(keyfile) as f:
|
with open(keyfile) as f:
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
key = RSA.import_key(f.read())
|
key = RSA.import_key(f.read())
|
||||||
f.close()
|
f.close()
|
||||||
publickey = key.publickey()
|
publickey = key.publickey()
|
||||||
@@ -1839,6 +1902,8 @@ class node:
|
|||||||
if keyfile is None:
|
if keyfile is None:
|
||||||
keyfile = self.key
|
keyfile = self.key
|
||||||
if keyfile is not None:
|
if keyfile is not None:
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Cipher import PKCS1_OAEP
|
||||||
with open(keyfile) as f:
|
with open(keyfile) as f:
|
||||||
key = RSA.import_key(f.read())
|
key = RSA.import_key(f.read())
|
||||||
decryptor = PKCS1_OAEP.new(key)
|
decryptor = PKCS1_OAEP.new(key)
|
||||||
@@ -4022,7 +4087,7 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
|
|||||||
<nav id="sidebar">
|
<nav id="sidebar">
|
||||||
<div class="toc">
|
<div class="toc">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#connpy-v610">Connpy (v6.1.0)</a><ul>
|
<li><a href="#connpy-v611">Connpy (v6.1.1)</a><ul>
|
||||||
<li><a href="#1-ai-system">1. 🤖 AI System</a><ul>
|
<li><a href="#1-ai-system">1. 🤖 AI System</a><ul>
|
||||||
<li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li>
|
<li><a href="#1a-terminal-copilot-ctrlspace">1a. Terminal Copilot (Ctrl+Space)</a></li>
|
||||||
<li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li>
|
<li><a href="#1b-ai-chat-conn-ai">1b. AI Chat (conn ai)</a></li>
|
||||||
@@ -4104,6 +4169,8 @@ def test(self, commands, expected, vars = None,*, folder = None, prompt = None,
|
|||||||
<li><code><a title="connpy.configfile.get_effective_setting" href="#connpy.configfile.get_effective_setting">get_effective_setting</a></code></li>
|
<li><code><a title="connpy.configfile.get_effective_setting" href="#connpy.configfile.get_effective_setting">get_effective_setting</a></code></li>
|
||||||
<li><code><a title="connpy.configfile.getitem" href="#connpy.configfile.getitem">getitem</a></code></li>
|
<li><code><a title="connpy.configfile.getitem" href="#connpy.configfile.getitem">getitem</a></code></li>
|
||||||
<li><code><a title="connpy.configfile.getitems" href="#connpy.configfile.getitems">getitems</a></code></li>
|
<li><code><a title="connpy.configfile.getitems" href="#connpy.configfile.getitems">getitems</a></code></li>
|
||||||
|
<li><code><a title="connpy.configfile.privatekey" href="#connpy.configfile.privatekey">privatekey</a></code></li>
|
||||||
|
<li><code><a title="connpy.configfile.publickey" href="#connpy.configfile.publickey">publickey</a></code></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
Reference in New Issue
Block a user