ntermqt 0.1.9__py3-none-any.whl → 0.1.11__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.
nterm/__main__.py CHANGED
@@ -197,11 +197,28 @@ class TerminalTab(QWidget):
197
197
  self.terminal = TerminalWidget()
198
198
  layout.addWidget(self.terminal)
199
199
 
200
- # Pass the vault to SSHSession for credential resolution
201
- vault = credential_resolver if credential_resolver else None
202
- self.ssh_session = SSHSession(profile, vault=vault)
200
+ # Create initial session
201
+ self._create_and_attach_session()
202
+
203
+ # Handle reconnect by creating fresh session
204
+ self.terminal.reconnect_requested.connect(self._on_reconnect)
205
+
206
+ def _create_and_attach_session(self):
207
+ """Create fresh SSHSession and attach to terminal."""
208
+ vault = self.credential_resolver if self.credential_resolver else None
209
+ self.ssh_session = SSHSession(self.profile, vault=vault)
203
210
  self.terminal.attach_session(self.ssh_session)
204
211
 
212
+ def _on_reconnect(self):
213
+ """Handle reconnect by building fresh session."""
214
+ print(f"Reconnecting: {self.session.name}")
215
+ try:
216
+ self.ssh_session.disconnect()
217
+ except Exception:
218
+ pass # Old session may already be dead
219
+ self._create_and_attach_session()
220
+ self.ssh_session.connect()
221
+
205
222
  def connect(self):
206
223
  """Start the SSH connection."""
207
224
  self.ssh_session.connect()
@@ -227,7 +244,6 @@ class TerminalTab(QWidget):
227
244
  # Default to True if we can't determine - safer to warn
228
245
  return True
229
246
 
230
-
231
247
  class LocalTerminalTab(QWidget):
232
248
  """A terminal tab for local processes (shell, IPython, etc.)."""
233
249
 
nterm/manager/io.py CHANGED
@@ -3,11 +3,14 @@ Session import/export functionality.
3
3
 
4
4
  Supports JSON format for portability.
5
5
  Also supports importing from TerminalTelemetry YAML format.
6
+ Also supports simple CSV import for quick session lists.
6
7
  """
7
8
 
8
9
  from __future__ import annotations
10
+ import csv
9
11
  import json
10
12
  import yaml
13
+ from io import StringIO
11
14
  from pathlib import Path
12
15
  from datetime import datetime
13
16
  from typing import Optional
@@ -17,9 +20,10 @@ from PyQt6.QtWidgets import (
17
20
  QWidget, QFileDialog, QMessageBox, QDialog,
18
21
  QVBoxLayout, QHBoxLayout, QLabel, QCheckBox,
19
22
  QPushButton, QDialogButtonBox, QTreeWidget,
20
- QTreeWidgetItem, QGroupBox
23
+ QTreeWidgetItem, QGroupBox, QComboBox, QTextEdit
21
24
  )
22
25
  from PyQt6.QtCore import Qt
26
+ from PyQt6.QtGui import QFont
23
27
 
24
28
  from .models import SessionStore, SavedSession, SessionFolder
25
29
 
@@ -189,6 +193,133 @@ def import_sessions(
189
193
  return imported, skipped
190
194
 
191
195
 
196
+ def import_sessions_csv(
197
+ store: SessionStore,
198
+ path: Path,
199
+ merge: bool = True,
200
+ folder_name: Optional[str] = None
201
+ ) -> tuple[int, int, int]:
202
+ """
203
+ Import sessions from CSV file.
204
+
205
+ Supports flexible column names:
206
+ - name/display_name/hostname → session name
207
+ - hostname/host/ip/address → connection hostname
208
+ - port → port (default 22)
209
+ - description/desc → description
210
+ - folder/folder_name/group → folder assignment
211
+
212
+ Args:
213
+ store: Session store instance
214
+ path: Input CSV file path
215
+ merge: If True, merge with existing. If False, skip duplicates.
216
+ folder_name: Override folder for all imported sessions (optional)
217
+
218
+ Returns:
219
+ Tuple of (folders_created, sessions_imported, sessions_skipped)
220
+ """
221
+ with open(path, newline='', encoding='utf-8-sig') as f:
222
+ # Sniff dialect and read
223
+ sample = f.read(4096)
224
+ f.seek(0)
225
+
226
+ try:
227
+ dialect = csv.Sniffer().sniff(sample)
228
+ except csv.Error:
229
+ dialect = csv.excel # Default to standard CSV
230
+
231
+ reader = csv.DictReader(f, dialect=dialect)
232
+
233
+ # Normalize header names (lowercase, strip whitespace)
234
+ if reader.fieldnames:
235
+ reader.fieldnames = [h.lower().strip() for h in reader.fieldnames]
236
+
237
+ rows = list(reader)
238
+
239
+ if not rows:
240
+ return 0, 0, 0
241
+
242
+ # Column name mappings (first match wins)
243
+ name_cols = ['name', 'display_name', 'session_name', 'device_name', 'device']
244
+ host_cols = ['hostname', 'host', 'ip', 'ip_address', 'address', 'mgmt_ip']
245
+ port_cols = ['port', 'ssh_port']
246
+ desc_cols = ['description', 'desc', 'notes', 'comment']
247
+ folder_cols = ['folder', 'folder_name', 'group', 'site', 'location']
248
+
249
+ def find_col(row: dict, candidates: list[str]) -> Optional[str]:
250
+ """Find first matching column value."""
251
+ for col in candidates:
252
+ if col in row and row[col]:
253
+ return row[col].strip()
254
+ return None
255
+
256
+ # Track folders and sessions
257
+ folders_created = 0
258
+ sessions_imported = 0
259
+ sessions_skipped = 0
260
+
261
+ existing_sessions = {s.hostname: s for s in store.list_all_sessions()}
262
+ folder_cache: dict[str, int] = {} # folder_name -> folder_id
263
+
264
+ for row in rows:
265
+ # Extract fields with fallbacks
266
+ hostname = find_col(row, host_cols)
267
+ if not hostname:
268
+ continue # Skip rows without hostname
269
+
270
+ name = find_col(row, name_cols) or hostname
271
+ port_str = find_col(row, port_cols)
272
+ port = int(port_str) if port_str and port_str.isdigit() else 22
273
+ description = find_col(row, desc_cols) or ""
274
+
275
+ # Determine folder
276
+ row_folder = folder_name or find_col(row, folder_cols)
277
+ folder_id = None
278
+
279
+ if row_folder:
280
+ if row_folder in folder_cache:
281
+ folder_id = folder_cache[row_folder]
282
+ else:
283
+ # Check if folder exists
284
+ existing_folders = store.list_folders(None)
285
+ existing = next((f for f in existing_folders if f.name == row_folder), None)
286
+
287
+ if existing:
288
+ folder_id = existing.id
289
+ else:
290
+ folder_id = store.add_folder(row_folder)
291
+ folders_created += 1
292
+
293
+ folder_cache[row_folder] = folder_id
294
+
295
+ # Check for duplicate
296
+ if hostname in existing_sessions and not merge:
297
+ sessions_skipped += 1
298
+ continue
299
+
300
+ session = SavedSession(
301
+ name=name,
302
+ description=description,
303
+ hostname=hostname,
304
+ port=port,
305
+ credential_name=None,
306
+ folder_id=folder_id,
307
+ )
308
+
309
+ # Update or insert
310
+ if hostname in existing_sessions and merge:
311
+ existing = existing_sessions[hostname]
312
+ session.id = existing.id
313
+ store.update_session(session)
314
+ else:
315
+ store.add_session(session)
316
+ existing_sessions[hostname] = session
317
+
318
+ sessions_imported += 1
319
+
320
+ return folders_created, sessions_imported, sessions_skipped
321
+
322
+
192
323
  def import_terminal_telemetry(
193
324
  store: SessionStore,
194
325
  path: Path,
@@ -359,6 +490,49 @@ class ExportDialog(QDialog):
359
490
  )
360
491
 
361
492
 
493
+ # =============================================================================
494
+ # Format Help Text
495
+ # =============================================================================
496
+
497
+ CSV_HELP_TEXT = """\
498
+ <b>CSV Format</b><br><br>
499
+ Simple comma-separated format for quick imports from spreadsheets or other tools.<br><br>
500
+
501
+ <b>Supported Columns:</b>
502
+ <table cellspacing="4">
503
+ <tr><td><code>name</code></td><td>Session display name (falls back to hostname)</td></tr>
504
+ <tr><td><code>hostname</code></td><td><b>Required.</b> IP address or DNS name</td></tr>
505
+ <tr><td><code>port</code></td><td>SSH port (default: 22)</td></tr>
506
+ <tr><td><code>description</code></td><td>Optional notes</td></tr>
507
+ <tr><td><code>folder</code></td><td>Folder name (created if missing)</td></tr>
508
+ </table>
509
+ <br>
510
+ <b>Example:</b><br>
511
+ <code>name,hostname,port,folder<br>
512
+ core-rtr-01,10.0.0.1,22,Core<br>
513
+ core-rtr-02,10.0.0.2,22,Core<br>
514
+ edge-sw-01,10.1.0.1,22,Edge</code><br><br>
515
+
516
+ <i>Column names are flexible: "host", "ip", "address" also work for hostname.</i>
517
+ """
518
+
519
+ JSON_HELP_TEXT = """\
520
+ <b>JSON Format</b><br><br>
521
+ Native nterm export format. Preserves folders, hierarchy, and all session metadata.<br><br>
522
+
523
+ <b>Structure:</b><br>
524
+ <code>{<br>
525
+ &nbsp;&nbsp;"version": 1,<br>
526
+ &nbsp;&nbsp;"folders": [{"id": 1, "name": "Site A", ...}],<br>
527
+ &nbsp;&nbsp;"sessions": [<br>
528
+ &nbsp;&nbsp;&nbsp;&nbsp;{"name": "router-01", "hostname": "10.0.0.1", "port": 22, "folder_id": 1}<br>
529
+ &nbsp;&nbsp;]<br>
530
+ }</code><br><br>
531
+
532
+ <b>Tip:</b> Use <i>Export Sessions</i> to create a template, then edit and re-import.
533
+ """
534
+
535
+
362
536
  class ImportDialog(QDialog):
363
537
  """Dialog for import options and preview."""
364
538
 
@@ -366,14 +540,45 @@ class ImportDialog(QDialog):
366
540
  super().__init__(parent)
367
541
  self.store = store
368
542
  self._import_path: Optional[Path] = None
369
- self._import_data: Optional[dict] = None
543
+ self._import_data = None # Can be dict (JSON) or list of rows (CSV)
544
+ self._import_format: str = "json"
370
545
 
371
546
  self.setWindowTitle("Import Sessions")
372
- self.setMinimumWidth(500)
373
- self.setMinimumHeight(400)
547
+ self.setMinimumWidth(600)
548
+ self.setMinimumHeight(500)
374
549
 
375
550
  layout = QVBoxLayout(self)
376
551
 
552
+ # Format selection row
553
+ format_row = QHBoxLayout()
554
+ format_row.addWidget(QLabel("Format:"))
555
+
556
+ self._format_combo = QComboBox()
557
+ self._format_combo.addItem("JSON (nterm native)", "json")
558
+ self._format_combo.addItem("CSV (spreadsheet)", "csv")
559
+ self._format_combo.currentIndexChanged.connect(self._on_format_changed)
560
+ self._format_combo.setMinimumWidth(180)
561
+ format_row.addWidget(self._format_combo)
562
+
563
+ format_row.addStretch()
564
+
565
+ # Help toggle
566
+ self._help_btn = QPushButton("? Help")
567
+ self._help_btn.setCheckable(True)
568
+ self._help_btn.setMaximumWidth(80)
569
+ self._help_btn.toggled.connect(self._toggle_help)
570
+ format_row.addWidget(self._help_btn)
571
+
572
+ layout.addLayout(format_row)
573
+
574
+ # Help panel (hidden by default)
575
+ self._help_panel = QTextEdit()
576
+ self._help_panel.setReadOnly(True)
577
+ self._help_panel.setMaximumHeight(180)
578
+ self._help_panel.setHtml(JSON_HELP_TEXT)
579
+ self._help_panel.hide()
580
+ layout.addWidget(self._help_panel)
581
+
377
582
  # File selection
378
583
  file_row = QHBoxLayout()
379
584
  self._file_label = QLabel("No file selected")
@@ -392,6 +597,7 @@ class ImportDialog(QDialog):
392
597
  self._preview_tree = QTreeWidget()
393
598
  self._preview_tree.setHeaderLabels(["Name", "Host", "Port"])
394
599
  self._preview_tree.setRootIsDecorated(True)
600
+ self._preview_tree.setAlternatingRowColors(True)
395
601
  preview_layout.addWidget(self._preview_tree)
396
602
 
397
603
  layout.addWidget(preview_group)
@@ -421,13 +627,41 @@ class ImportDialog(QDialog):
421
627
  self._button_box.button(QDialogButtonBox.StandardButton.Ok).setText("Import")
422
628
  layout.addWidget(self._button_box)
423
629
 
630
+ def _on_format_changed(self, index: int) -> None:
631
+ """Handle format selection change."""
632
+ self._import_format = self._format_combo.currentData()
633
+
634
+ # Update help text
635
+ if self._import_format == "csv":
636
+ self._help_panel.setHtml(CSV_HELP_TEXT)
637
+ else:
638
+ self._help_panel.setHtml(JSON_HELP_TEXT)
639
+
640
+ # Clear preview if format changed after file loaded
641
+ if self._import_path:
642
+ self._preview_tree.clear()
643
+ self._import_path = None
644
+ self._import_data = None
645
+ self._file_label.setText("No file selected")
646
+ self._button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(False)
647
+
648
+ def _toggle_help(self, show: bool) -> None:
649
+ """Show/hide help panel."""
650
+ self._help_panel.setVisible(show)
651
+ self._help_btn.setText("▼ Help" if show else "? Help")
652
+
424
653
  def _browse_file(self) -> None:
425
654
  """Browse for import file."""
655
+ if self._import_format == "csv":
656
+ filter_str = "CSV Files (*.csv);;All Files (*)"
657
+ else:
658
+ filter_str = "JSON Files (*.json);;All Files (*)"
659
+
426
660
  path, _ = QFileDialog.getOpenFileName(
427
661
  self,
428
662
  "Import Sessions",
429
663
  "",
430
- "JSON Files (*.json);;All Files (*)"
664
+ filter_str
431
665
  )
432
666
 
433
667
  if path:
@@ -436,47 +670,19 @@ class ImportDialog(QDialog):
436
670
  def _load_preview(self, path: Path) -> None:
437
671
  """Load and preview import file."""
438
672
  try:
439
- with open(path) as f:
440
- data = json.load(f)
673
+ self._preview_tree.clear()
674
+
675
+ if self._import_format == "csv":
676
+ self._load_csv_preview(path)
677
+ else:
678
+ self._load_json_preview(path)
441
679
 
442
680
  self._import_path = path
443
- self._import_data = data
444
681
  self._file_label.setText(path.name)
445
682
 
446
- # Build preview tree
447
- self._preview_tree.clear()
448
-
449
- # Create folder items
450
- folder_items: dict[int, QTreeWidgetItem] = {}
451
- for folder_data in data.get("folders", []):
452
- item = QTreeWidgetItem()
453
- item.setText(0, f"📁 {folder_data['name']}")
454
- folder_items[folder_data["id"]] = item
455
-
456
- # Parent folders
457
- for folder_data in data.get("folders", []):
458
- item = folder_items[folder_data["id"]]
459
- parent_id = folder_data.get("parent_id")
460
- if parent_id and parent_id in folder_items:
461
- folder_items[parent_id].addChild(item)
462
- else:
463
- self._preview_tree.addTopLevelItem(item)
464
-
465
- # Add sessions
466
- for session_data in data.get("sessions", []):
467
- item = QTreeWidgetItem()
468
- item.setText(0, session_data.get("name", ""))
469
- item.setText(1, session_data.get("hostname", ""))
470
- item.setText(2, str(session_data.get("port", 22)))
471
-
472
- folder_id = session_data.get("folder_id")
473
- if folder_id and folder_id in folder_items:
474
- folder_items[folder_id].addChild(item)
475
- else:
476
- self._preview_tree.addTopLevelItem(item)
477
-
478
683
  self._preview_tree.expandAll()
479
- self._preview_tree.resizeColumnToContents(0)
684
+ for i in range(3):
685
+ self._preview_tree.resizeColumnToContents(i)
480
686
 
481
687
  # Enable import button
482
688
  self._button_box.button(QDialogButtonBox.StandardButton.Ok).setEnabled(True)
@@ -488,19 +694,126 @@ class ImportDialog(QDialog):
488
694
  f"Failed to load file:\n{e}"
489
695
  )
490
696
 
697
+ def _load_csv_preview(self, path: Path) -> None:
698
+ """Load CSV file and populate preview."""
699
+ with open(path, newline='', encoding='utf-8-sig') as f:
700
+ sample = f.read(4096)
701
+ f.seek(0)
702
+
703
+ try:
704
+ dialect = csv.Sniffer().sniff(sample)
705
+ except csv.Error:
706
+ dialect = csv.excel
707
+
708
+ reader = csv.DictReader(f, dialect=dialect)
709
+ if reader.fieldnames:
710
+ reader.fieldnames = [h.lower().strip() for h in reader.fieldnames]
711
+
712
+ rows = list(reader)
713
+
714
+ self._import_data = rows
715
+
716
+ # Column mappings
717
+ name_cols = ['name', 'display_name', 'session_name', 'device_name', 'device']
718
+ host_cols = ['hostname', 'host', 'ip', 'ip_address', 'address', 'mgmt_ip']
719
+ port_cols = ['port', 'ssh_port']
720
+ folder_cols = ['folder', 'folder_name', 'group', 'site', 'location']
721
+
722
+ def find_col(row: dict, candidates: list[str]) -> Optional[str]:
723
+ for col in candidates:
724
+ if col in row and row[col]:
725
+ return row[col].strip()
726
+ return None
727
+
728
+ # Group by folder for preview
729
+ folder_items: dict[str, QTreeWidgetItem] = {}
730
+ root_sessions: list[QTreeWidgetItem] = []
731
+
732
+ for row in rows:
733
+ hostname = find_col(row, host_cols)
734
+ if not hostname:
735
+ continue
736
+
737
+ name = find_col(row, name_cols) or hostname
738
+ port = find_col(row, port_cols) or "22"
739
+ folder = find_col(row, folder_cols)
740
+
741
+ item = QTreeWidgetItem()
742
+ item.setText(0, name)
743
+ item.setText(1, hostname)
744
+ item.setText(2, port)
745
+
746
+ if folder:
747
+ if folder not in folder_items:
748
+ folder_item = QTreeWidgetItem()
749
+ folder_item.setText(0, f"📁 {folder}")
750
+ self._preview_tree.addTopLevelItem(folder_item)
751
+ folder_items[folder] = folder_item
752
+ folder_items[folder].addChild(item)
753
+ else:
754
+ root_sessions.append(item)
755
+
756
+ # Add ungrouped sessions at root
757
+ for item in root_sessions:
758
+ self._preview_tree.addTopLevelItem(item)
759
+
760
+ def _load_json_preview(self, path: Path) -> None:
761
+ """Load JSON file and populate preview."""
762
+ with open(path) as f:
763
+ data = json.load(f)
764
+
765
+ self._import_data = data
766
+
767
+ # Create folder items
768
+ folder_items: dict[int, QTreeWidgetItem] = {}
769
+ for folder_data in data.get("folders", []):
770
+ item = QTreeWidgetItem()
771
+ item.setText(0, f"📁 {folder_data['name']}")
772
+ folder_items[folder_data["id"]] = item
773
+
774
+ # Parent folders
775
+ for folder_data in data.get("folders", []):
776
+ item = folder_items[folder_data["id"]]
777
+ parent_id = folder_data.get("parent_id")
778
+ if parent_id and parent_id in folder_items:
779
+ folder_items[parent_id].addChild(item)
780
+ else:
781
+ self._preview_tree.addTopLevelItem(item)
782
+
783
+ # Add sessions
784
+ for session_data in data.get("sessions", []):
785
+ item = QTreeWidgetItem()
786
+ item.setText(0, session_data.get("name", ""))
787
+ item.setText(1, session_data.get("hostname", ""))
788
+ item.setText(2, str(session_data.get("port", 22)))
789
+
790
+ folder_id = session_data.get("folder_id")
791
+ if folder_id and folder_id in folder_items:
792
+ folder_items[folder_id].addChild(item)
793
+ else:
794
+ self._preview_tree.addTopLevelItem(item)
795
+
491
796
  def _on_import(self) -> None:
492
797
  """Perform import."""
493
798
  if not self._import_path:
494
799
  return
495
800
 
496
801
  try:
497
- imported, skipped = import_sessions(
498
- self.store,
499
- self._import_path,
500
- merge=self._merge_check.isChecked()
501
- )
802
+ if self._import_format == "csv":
803
+ folders, imported, skipped = import_sessions_csv(
804
+ self.store,
805
+ self._import_path,
806
+ merge=self._merge_check.isChecked()
807
+ )
808
+ msg = f"Created {folders} folders.\nImported {imported} sessions."
809
+ else:
810
+ imported, skipped = import_sessions(
811
+ self.store,
812
+ self._import_path,
813
+ merge=self._merge_check.isChecked()
814
+ )
815
+ msg = f"Imported {imported} sessions."
502
816
 
503
- msg = f"Imported {imported} sessions."
504
817
  if skipped:
505
818
  msg += f"\nSkipped {skipped} duplicates."
506
819
 
nterm/terminal/widget.py CHANGED
@@ -374,8 +374,8 @@ class TerminalWidget(QWidget):
374
374
  self.hide_overlay()
375
375
  self.reconnect_requested.emit()
376
376
  # Actually trigger the reconnection
377
- if self._session:
378
- self._session.connect()
377
+ # if self._session:
378
+ # self._session.connect()
379
379
  else:
380
380
  # First keypress - show prompt
381
381
  self._awaiting_reconnect_confirm = True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ntermqt
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: Modern SSH terminal widget for PyQt6 with credential vault and jump host support
5
5
  Author: Scott Peterman
6
6
  License: GPL-3.0
@@ -1,5 +1,5 @@
1
1
  nterm/__init__.py,sha256=Liu1bya6xi3NnwO9KLqqlYyLD1eS-2HeEdEpJRc2480,1346
2
- nterm/__main__.py,sha256=sushq7oXsyBk6pLEff7qDSWck7Emk1QxxKLIfPHh0HY,35546
2
+ nterm/__main__.py,sha256=KLdsyVjic3YKYL9zg6J7DAYECRlSJVuznuTkJoPD8T4,36149
3
3
  nterm/config.py,sha256=19T28opP-rdLRuxXCGP-qrklAlh4HNbXNTyAwveBhu8,4690
4
4
  nterm/resources.py,sha256=SYC8JeF7vVfER93KKRd-tt5b25t0tHTkd7fSJqVDXnI,1447
5
5
  nterm/askpass/__init__.py,sha256=UpJBk0EOm0nkRwMVv7YdIB4v75ZJpSYmNsU_GlgzbUg,495
@@ -9,7 +9,7 @@ nterm/connection/profile.py,sha256=4RMgnRNKCc-dFGEIpmQc_bob5MtzxO04_PljP-qUGLs,9
9
9
  nterm/manager/__init__.py,sha256=_QIeTap5CTL3jdTS1Q16fAt-PrqcNPUVr9gtJ22f0ng,774
10
10
  nterm/manager/connect_dialog.py,sha256=yd8g_gYttT_UdflRxSfyss8OQTfrvKLUOMg4Kj8FPNo,11711
11
11
  nterm/manager/editor.py,sha256=Fn2YWHJ1EwPYrhKhsi4GTBYwRfCYsHsqgKkLY-LQ8JI,8469
12
- nterm/manager/io.py,sha256=R5ksWgpEz0VdVCokcgTN5G3PFgp5QYhjjt40OypSWkY,21687
12
+ nterm/manager/io.py,sha256=59ehTfnS1sAKEEEwMxujHuccV7rYSoRx4vfS1ExDqW4,32572
13
13
  nterm/manager/models.py,sha256=cvC2HzCRadNG1EYsnZN4C9YS6uolHGcUGGZtt-wzGF4,12237
14
14
  nterm/manager/settings.py,sha256=r6MTw_9r1Wl2UX_ALpXIuPbDvJ0D91Y8wRKq6Bfr_3g,9210
15
15
  nterm/manager/tree.py,sha256=I78wSjkSuyM6903II-XNyPug9saMSODUNBCHCDrq4ls,22397
@@ -38,7 +38,7 @@ nterm/session/pty_transport.py,sha256=QwSFqKKuJhgcLWzv1CUKf3aCGDGbbkmmGwIB1L1A2P
38
38
  nterm/session/ssh.py,sha256=sGOxjBa9FX6GjVwkmfiKsupoLVsrPVk-LSREjlNmAdE,20942
39
39
  nterm/terminal/__init__.py,sha256=uFnG366Z166pK-ijT1dZanVSSFVZCiMGeNKXvss_sDg,184
40
40
  nterm/terminal/bridge.py,sha256=mSkxZr3UGyaFI14w08dzekCkOhfUetq0GIjrBtA3qI0,3199
41
- nterm/terminal/widget.py,sha256=mxUrQxFmigNR6S3vgnzHahTRGYQI2bNYTBqNg47yaR8,15716
41
+ nterm/terminal/widget.py,sha256=38EyMmv6oz19ry0I0tQ-9itSK8LoU5vz1uuXJFz8swg,15720
42
42
  nterm/terminal/resources/terminal.html,sha256=1onb3qUdDa0qzETR8XaKx0UR6BPlCm_ZpMFVgt36ZPA,7985
43
43
  nterm/terminal/resources/terminal.js,sha256=zW9n1MRujSXv66ENgU-gzk_mc75EpWye_f88ejChSW4,13852
44
44
  nterm/terminal/resources/xterm-addon-fit.min.js,sha256=x45XlcZIes3ySrQ2eY1KnOw4SBAbKBvGWwYfOdtxS-E,1789
@@ -66,8 +66,8 @@ nterm/vault/manager_ui.py,sha256=qle-W40j6L_pOR0AaOCeyU8myizFTRkISNrloCn0H_Y,345
66
66
  nterm/vault/profile.py,sha256=qM9TJf68RKdjtxo-sJehO7wS4iTi2G26BKbmlmHLA5M,6246
67
67
  nterm/vault/resolver.py,sha256=GWB2YR9H1MH98RGQBKvitIsjWT_-wSMLuddZNz4wbns,7800
68
68
  nterm/vault/store.py,sha256=_0Lfe0WKjm3uSAtxgn9qAPlpBOLCuq9SVgzqsE_qaGQ,21199
69
- ntermqt-0.1.9.dist-info/METADATA,sha256=ICYH7EjefVJLe_Hp-prpK8GPQE2TTqEaoX7-x2M0cOo,16040
70
- ntermqt-0.1.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
71
- ntermqt-0.1.9.dist-info/entry_points.txt,sha256=Gunr-_3w-aSpfqoMuGKM2PJSCRo9hZ7K1BksUtp1yd8,130
72
- ntermqt-0.1.9.dist-info/top_level.txt,sha256=bZdnNLTHNRNqo9jsOQGUWF7h5st0xW_thH0n2QOxWUo,6
73
- ntermqt-0.1.9.dist-info/RECORD,,
69
+ ntermqt-0.1.11.dist-info/METADATA,sha256=VC3c1CiPEAwtFhWzHIQK6spEj4MtRRNtZ6QUvwAkdIc,16041
70
+ ntermqt-0.1.11.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
71
+ ntermqt-0.1.11.dist-info/entry_points.txt,sha256=Gunr-_3w-aSpfqoMuGKM2PJSCRo9hZ7K1BksUtp1yd8,130
72
+ ntermqt-0.1.11.dist-info/top_level.txt,sha256=bZdnNLTHNRNqo9jsOQGUWF7h5st0xW_thH0n2QOxWUo,6
73
+ ntermqt-0.1.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5