ntermqt 0.1.8__py3-none-any.whl → 0.1.10__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/examples/basic_terminal.py +415 -0
- nterm/manager/io.py +360 -47
- nterm/scripting/api.py +199 -35
- nterm/scripting/platform_utils.py +269 -3
- nterm/scripting/repl.py +230 -71
- nterm/scripting/repl_interactive.py +27 -2
- {ntermqt-0.1.8.dist-info → ntermqt-0.1.10.dist-info}/METADATA +2 -2
- {ntermqt-0.1.8.dist-info → ntermqt-0.1.10.dist-info}/RECORD +11 -10
- {ntermqt-0.1.8.dist-info → ntermqt-0.1.10.dist-info}/WHEEL +1 -1
- {ntermqt-0.1.8.dist-info → ntermqt-0.1.10.dist-info}/entry_points.txt +0 -0
- {ntermqt-0.1.8.dist-info → ntermqt-0.1.10.dist-info}/top_level.txt +0 -0
nterm/scripting/repl.py
CHANGED
|
@@ -102,6 +102,8 @@ class NTermREPL:
|
|
|
102
102
|
:folders List folders
|
|
103
103
|
:connect <device> Connect to device [--cred name] [--debug]
|
|
104
104
|
:disconnect Disconnect current session
|
|
105
|
+
:disconnect_all Disconnect all sessions
|
|
106
|
+
:switch <device> Switch to another active session
|
|
105
107
|
:sessions List all active sessions
|
|
106
108
|
:policy [mode] Get/set policy (read_only|ops)
|
|
107
109
|
:mode [raw|parsed] Get/set output mode
|
|
@@ -120,7 +122,7 @@ class NTermREPL:
|
|
|
120
122
|
:neighbors Fetch CDP/LLDP neighbors (tries both)
|
|
121
123
|
:bgp Fetch BGP summary
|
|
122
124
|
:routes Fetch routing table
|
|
123
|
-
:intf <
|
|
125
|
+
:intf <n> Fetch specific interface details
|
|
124
126
|
|
|
125
127
|
Raw Commands:
|
|
126
128
|
(anything else) Runs as CLI on the connected session
|
|
@@ -232,9 +234,26 @@ class NTermREPL:
|
|
|
232
234
|
if not self.state.api.vault_unlocked:
|
|
233
235
|
return self._err("Vault is locked. Run :unlock first.")
|
|
234
236
|
|
|
235
|
-
#
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
# Check if already connected to this device
|
|
238
|
+
existing_sessions = self.state.api.active_sessions()
|
|
239
|
+
for sess in existing_sessions:
|
|
240
|
+
if sess.device_name == device:
|
|
241
|
+
# Already connected - just switch to it
|
|
242
|
+
self.state.session = sess
|
|
243
|
+
self.state.connected_device = sess.device_name
|
|
244
|
+
return self._ok({
|
|
245
|
+
"type": "switch",
|
|
246
|
+
"device": sess.device_name,
|
|
247
|
+
"hostname": sess.hostname,
|
|
248
|
+
"port": sess.port,
|
|
249
|
+
"platform": sess.platform,
|
|
250
|
+
"prompt": sess.prompt,
|
|
251
|
+
"message": "Already connected - switched to existing session",
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
# NOTE: We no longer disconnect the existing session!
|
|
255
|
+
# Old sessions stay active in the background.
|
|
256
|
+
# User can switch back with :switch or disconnect with :disconnect
|
|
238
257
|
|
|
239
258
|
try:
|
|
240
259
|
sess = self.state.api.connect(device, credential=cred, debug=debug)
|
|
@@ -255,8 +274,60 @@ class NTermREPL:
|
|
|
255
274
|
if cmd == ":disconnect":
|
|
256
275
|
if not self.state.session:
|
|
257
276
|
return self._ok({"type": "disconnect", "message": "No active session"})
|
|
277
|
+
|
|
278
|
+
device_name = self.state.connected_device
|
|
258
279
|
self._safe_disconnect()
|
|
259
|
-
|
|
280
|
+
|
|
281
|
+
# Try to switch to another active session if available
|
|
282
|
+
remaining = self.state.api.active_sessions()
|
|
283
|
+
if remaining:
|
|
284
|
+
self.state.session = remaining[0]
|
|
285
|
+
self.state.connected_device = remaining[0].device_name
|
|
286
|
+
return self._ok({
|
|
287
|
+
"type": "disconnect",
|
|
288
|
+
"disconnected": device_name,
|
|
289
|
+
"switched_to": self.state.connected_device,
|
|
290
|
+
"message": f"Disconnected {device_name}, switched to {self.state.connected_device}",
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
return self._ok({"type": "disconnect", "disconnected": device_name})
|
|
294
|
+
|
|
295
|
+
if cmd == ":disconnect_all":
|
|
296
|
+
count = self.state.api.disconnect_all()
|
|
297
|
+
self.state.session = None
|
|
298
|
+
self.state.connected_device = None
|
|
299
|
+
return self._ok({"type": "disconnect_all", "count": count})
|
|
300
|
+
|
|
301
|
+
if cmd == ":switch":
|
|
302
|
+
if len(parts) < 2:
|
|
303
|
+
# Show available sessions
|
|
304
|
+
sessions = self.state.api.active_sessions()
|
|
305
|
+
if not sessions:
|
|
306
|
+
return self._err("No active sessions. Use :connect <device> first.")
|
|
307
|
+
|
|
308
|
+
session_names = [s.device_name for s in sessions]
|
|
309
|
+
return self._err(f"Usage: :switch <device>\nActive sessions: {', '.join(session_names)}")
|
|
310
|
+
|
|
311
|
+
target_device = parts[1]
|
|
312
|
+
|
|
313
|
+
# Find the session
|
|
314
|
+
sessions = self.state.api.active_sessions()
|
|
315
|
+
for sess in sessions:
|
|
316
|
+
if sess.device_name == target_device:
|
|
317
|
+
self.state.session = sess
|
|
318
|
+
self.state.connected_device = sess.device_name
|
|
319
|
+
return self._ok({
|
|
320
|
+
"type": "switch",
|
|
321
|
+
"device": sess.device_name,
|
|
322
|
+
"hostname": sess.hostname,
|
|
323
|
+
"port": sess.port,
|
|
324
|
+
"platform": sess.platform,
|
|
325
|
+
"prompt": sess.prompt,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
# Not found
|
|
329
|
+
session_names = [s.device_name for s in sessions]
|
|
330
|
+
return self._err(f"Session '{target_device}' not found.\nActive sessions: {', '.join(session_names)}")
|
|
260
331
|
|
|
261
332
|
if cmd == ":sessions":
|
|
262
333
|
sessions = self.state.api.active_sessions()
|
|
@@ -295,7 +366,7 @@ class NTermREPL:
|
|
|
295
366
|
})
|
|
296
367
|
mode = parts[1].lower()
|
|
297
368
|
if mode not in ["raw", "parsed"]:
|
|
298
|
-
return self._err("Mode must be
|
|
369
|
+
return self._err("Mode must be raw or parsed")
|
|
299
370
|
self.state.output_mode = mode
|
|
300
371
|
return self._ok({"type": "mode", "mode": mode})
|
|
301
372
|
|
|
@@ -304,113 +375,87 @@ class NTermREPL:
|
|
|
304
375
|
return self._ok({"type": "format", "format": self.state.output_format})
|
|
305
376
|
fmt = parts[1].lower()
|
|
306
377
|
if fmt not in ["text", "rich", "json"]:
|
|
307
|
-
return self._err("Format must be
|
|
378
|
+
return self._err("Format must be text, rich, or json")
|
|
308
379
|
self.state.output_format = fmt
|
|
309
380
|
return self._ok({"type": "format", "format": fmt})
|
|
310
381
|
|
|
311
382
|
if cmd == ":set_hint":
|
|
312
383
|
if len(parts) < 2:
|
|
313
384
|
return self._err("Usage: :set_hint <platform> (e.g., cisco_ios, arista_eos)")
|
|
314
|
-
|
|
315
|
-
self.state.platform_hint
|
|
316
|
-
return self._ok({"type": "set_hint", "platform_hint": platform})
|
|
385
|
+
self.state.platform_hint = parts[1]
|
|
386
|
+
return self._ok({"type": "set_hint", "platform_hint": self.state.platform_hint})
|
|
317
387
|
|
|
318
388
|
if cmd == ":clear_hint":
|
|
319
389
|
self.state.platform_hint = None
|
|
320
390
|
return self._ok({"type": "clear_hint"})
|
|
321
391
|
|
|
322
392
|
if cmd == ":debug":
|
|
323
|
-
if len(parts)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
elif mode in ["off", "false", "0"]:
|
|
328
|
-
self.state.debug_mode = False
|
|
329
|
-
else:
|
|
330
|
-
return self._err("Debug mode must be on or off")
|
|
331
|
-
else:
|
|
332
|
-
self.state.debug_mode = not self.state.debug_mode
|
|
393
|
+
if len(parts) < 2:
|
|
394
|
+
return self._ok({"type": "debug", "debug_mode": self.state.debug_mode})
|
|
395
|
+
val = parts[1].lower()
|
|
396
|
+
self.state.debug_mode = val in ["on", "true", "1", "yes"]
|
|
333
397
|
return self._ok({"type": "debug", "debug_mode": self.state.debug_mode})
|
|
334
398
|
|
|
335
399
|
if cmd == ":dbinfo":
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
return self._ok({"type": "dbinfo", "db_info": db_info})
|
|
339
|
-
except Exception as e:
|
|
340
|
-
return self._err(f"Failed to get DB info: {e}")
|
|
400
|
+
info = self.state.api.db_info()
|
|
401
|
+
return self._ok({"type": "dbinfo", "db_info": info})
|
|
341
402
|
|
|
342
|
-
# ===== Quick Commands
|
|
403
|
+
# ===== Quick Commands =====
|
|
343
404
|
if cmd == ":config":
|
|
344
|
-
return self.
|
|
405
|
+
return self._quick_config()
|
|
345
406
|
|
|
346
407
|
if cmd == ":version":
|
|
347
408
|
return self._quick_version()
|
|
348
409
|
|
|
349
410
|
if cmd == ":interfaces":
|
|
350
|
-
return self.
|
|
411
|
+
return self._quick_interfaces()
|
|
351
412
|
|
|
352
413
|
if cmd == ":neighbors":
|
|
353
414
|
return self._quick_neighbors()
|
|
354
415
|
|
|
355
416
|
if cmd == ":bgp":
|
|
356
|
-
return self.
|
|
417
|
+
return self._quick_bgp()
|
|
357
418
|
|
|
358
419
|
if cmd == ":routes":
|
|
359
|
-
return self.
|
|
420
|
+
return self._quick_routes()
|
|
360
421
|
|
|
361
422
|
if cmd == ":intf":
|
|
362
423
|
if len(parts) < 2:
|
|
363
|
-
return self._err("Usage: :intf <
|
|
364
|
-
|
|
365
|
-
return self._quick_command('interface_detail', parse=True, name=intf_name)
|
|
424
|
+
return self._err("Usage: :intf <interface> (e.g., :intf Gi0/1)")
|
|
425
|
+
return self._quick_interface_detail(parts[1])
|
|
366
426
|
|
|
367
427
|
return self._err(f"Unknown REPL command: {cmd}")
|
|
368
428
|
|
|
369
429
|
# -----------------------
|
|
370
|
-
# Quick
|
|
430
|
+
# Quick commands
|
|
371
431
|
# -----------------------
|
|
372
432
|
|
|
373
|
-
def
|
|
374
|
-
|
|
375
|
-
command_type: str,
|
|
376
|
-
parse: bool = True,
|
|
377
|
-
timeout: int = 30,
|
|
378
|
-
**kwargs
|
|
379
|
-
) -> Dict[str, Any]:
|
|
380
|
-
"""Execute a platform-aware command."""
|
|
433
|
+
def _quick_config(self) -> Dict[str, Any]:
|
|
434
|
+
"""Fetch running configuration."""
|
|
381
435
|
if not self.state.session:
|
|
382
436
|
return self._err("Not connected. Use :connect <device>")
|
|
383
437
|
|
|
384
|
-
platform = self.state.platform_hint or self.state.session.platform
|
|
385
|
-
cmd = get_platform_command(platform, command_type, **kwargs)
|
|
386
|
-
|
|
387
|
-
if not cmd:
|
|
388
|
-
return self._err(f"Command '{command_type}' not available for platform '{platform}'")
|
|
389
|
-
|
|
390
438
|
try:
|
|
391
439
|
started = time.time()
|
|
392
|
-
result = self.state.api.
|
|
440
|
+
result = self.state.api.send_platform_command(
|
|
393
441
|
self.state.session,
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
normalize=True,
|
|
442
|
+
'config',
|
|
443
|
+
parse=False, # Config is typically not parsed
|
|
444
|
+
timeout=60,
|
|
398
445
|
)
|
|
399
446
|
elapsed = time.time() - started
|
|
400
447
|
|
|
448
|
+
if not result:
|
|
449
|
+
return self._err("Config command not available for this platform")
|
|
450
|
+
|
|
401
451
|
payload = result.to_dict()
|
|
402
452
|
payload["elapsed_seconds"] = round(elapsed, 3)
|
|
403
|
-
payload["command_type"] =
|
|
404
|
-
|
|
405
|
-
# Truncate if needed
|
|
406
|
-
raw = payload.get("raw_output", "")
|
|
407
|
-
if len(raw) > self.state.policy.max_output_chars:
|
|
408
|
-
payload["raw_output"] = raw[:self.state.policy.max_output_chars] + "\n...<truncated>..."
|
|
453
|
+
payload["command_type"] = "config"
|
|
409
454
|
|
|
410
|
-
return self._ok({"type": "
|
|
455
|
+
return self._ok({"type": "config", "result": payload})
|
|
411
456
|
|
|
412
457
|
except Exception as e:
|
|
413
|
-
return self._err(f"
|
|
458
|
+
return self._err(f"Config fetch failed: {e}")
|
|
414
459
|
|
|
415
460
|
def _quick_version(self) -> Dict[str, Any]:
|
|
416
461
|
"""Fetch and extract version info."""
|
|
@@ -446,6 +491,33 @@ class NTermREPL:
|
|
|
446
491
|
except Exception as e:
|
|
447
492
|
return self._err(f"Version command failed: {e}")
|
|
448
493
|
|
|
494
|
+
def _quick_interfaces(self) -> Dict[str, Any]:
|
|
495
|
+
"""Fetch interface status."""
|
|
496
|
+
if not self.state.session:
|
|
497
|
+
return self._err("Not connected. Use :connect <device>")
|
|
498
|
+
|
|
499
|
+
try:
|
|
500
|
+
started = time.time()
|
|
501
|
+
result = self.state.api.send_platform_command(
|
|
502
|
+
self.state.session,
|
|
503
|
+
'interfaces_status',
|
|
504
|
+
parse=True,
|
|
505
|
+
timeout=30,
|
|
506
|
+
)
|
|
507
|
+
elapsed = time.time() - started
|
|
508
|
+
|
|
509
|
+
if not result:
|
|
510
|
+
return self._err("Interfaces command not available for this platform")
|
|
511
|
+
|
|
512
|
+
payload = result.to_dict()
|
|
513
|
+
payload["elapsed_seconds"] = round(elapsed, 3)
|
|
514
|
+
payload["command_type"] = "interfaces"
|
|
515
|
+
|
|
516
|
+
return self._ok({"type": "interfaces", "result": payload})
|
|
517
|
+
|
|
518
|
+
except Exception as e:
|
|
519
|
+
return self._err(f"Interfaces command failed: {e}")
|
|
520
|
+
|
|
449
521
|
def _quick_neighbors(self) -> Dict[str, Any]:
|
|
450
522
|
"""Fetch CDP/LLDP neighbors with fallback."""
|
|
451
523
|
if not self.state.session:
|
|
@@ -513,6 +585,89 @@ class NTermREPL:
|
|
|
513
585
|
except Exception as e:
|
|
514
586
|
return self._err(f"Neighbor discovery failed: {e}")
|
|
515
587
|
|
|
588
|
+
def _quick_bgp(self) -> Dict[str, Any]:
|
|
589
|
+
"""Fetch BGP summary."""
|
|
590
|
+
if not self.state.session:
|
|
591
|
+
return self._err("Not connected. Use :connect <device>")
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
started = time.time()
|
|
595
|
+
result = self.state.api.send_platform_command(
|
|
596
|
+
self.state.session,
|
|
597
|
+
'bgp_summary',
|
|
598
|
+
parse=True,
|
|
599
|
+
timeout=30,
|
|
600
|
+
)
|
|
601
|
+
elapsed = time.time() - started
|
|
602
|
+
|
|
603
|
+
if not result:
|
|
604
|
+
return self._err("BGP command not available for this platform")
|
|
605
|
+
|
|
606
|
+
payload = result.to_dict()
|
|
607
|
+
payload["elapsed_seconds"] = round(elapsed, 3)
|
|
608
|
+
payload["command_type"] = "bgp"
|
|
609
|
+
|
|
610
|
+
return self._ok({"type": "bgp", "result": payload})
|
|
611
|
+
|
|
612
|
+
except Exception as e:
|
|
613
|
+
return self._err(f"BGP command failed: {e}")
|
|
614
|
+
|
|
615
|
+
def _quick_routes(self) -> Dict[str, Any]:
|
|
616
|
+
"""Fetch routing table."""
|
|
617
|
+
if not self.state.session:
|
|
618
|
+
return self._err("Not connected. Use :connect <device>")
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
started = time.time()
|
|
622
|
+
result = self.state.api.send_platform_command(
|
|
623
|
+
self.state.session,
|
|
624
|
+
'routing_table',
|
|
625
|
+
parse=True,
|
|
626
|
+
timeout=30,
|
|
627
|
+
)
|
|
628
|
+
elapsed = time.time() - started
|
|
629
|
+
|
|
630
|
+
if not result:
|
|
631
|
+
return self._err("Routing command not available for this platform")
|
|
632
|
+
|
|
633
|
+
payload = result.to_dict()
|
|
634
|
+
payload["elapsed_seconds"] = round(elapsed, 3)
|
|
635
|
+
payload["command_type"] = "routes"
|
|
636
|
+
|
|
637
|
+
return self._ok({"type": "routes", "result": payload})
|
|
638
|
+
|
|
639
|
+
except Exception as e:
|
|
640
|
+
return self._err(f"Routing command failed: {e}")
|
|
641
|
+
|
|
642
|
+
def _quick_interface_detail(self, interface: str) -> Dict[str, Any]:
|
|
643
|
+
"""Fetch specific interface details."""
|
|
644
|
+
if not self.state.session:
|
|
645
|
+
return self._err("Not connected. Use :connect <device>")
|
|
646
|
+
|
|
647
|
+
try:
|
|
648
|
+
started = time.time()
|
|
649
|
+
result = self.state.api.send_platform_command(
|
|
650
|
+
self.state.session,
|
|
651
|
+
'interface_detail',
|
|
652
|
+
name=interface,
|
|
653
|
+
parse=True,
|
|
654
|
+
timeout=30,
|
|
655
|
+
)
|
|
656
|
+
elapsed = time.time() - started
|
|
657
|
+
|
|
658
|
+
if not result:
|
|
659
|
+
return self._err(f"Interface detail command not available for this platform")
|
|
660
|
+
|
|
661
|
+
payload = result.to_dict()
|
|
662
|
+
payload["elapsed_seconds"] = round(elapsed, 3)
|
|
663
|
+
payload["command_type"] = "interface_detail"
|
|
664
|
+
payload["interface"] = interface
|
|
665
|
+
|
|
666
|
+
return self._ok({"type": "interface_detail", "result": payload})
|
|
667
|
+
|
|
668
|
+
except Exception as e:
|
|
669
|
+
return self._err(f"Interface detail command failed: {e}")
|
|
670
|
+
|
|
516
671
|
# -----------------------
|
|
517
672
|
# CLI send path
|
|
518
673
|
# -----------------------
|
|
@@ -568,6 +723,7 @@ class NTermREPL:
|
|
|
568
723
|
# -----------------------
|
|
569
724
|
|
|
570
725
|
def _safe_disconnect(self) -> None:
|
|
726
|
+
"""Disconnect current session only (not all sessions)."""
|
|
571
727
|
if self.state.session:
|
|
572
728
|
try:
|
|
573
729
|
self.state.api.disconnect(self.state.session)
|
|
@@ -601,6 +757,8 @@ Inventory:
|
|
|
601
757
|
Sessions:
|
|
602
758
|
:connect <device> Connect to device [--cred name] [--debug]
|
|
603
759
|
:disconnect Disconnect current session
|
|
760
|
+
:disconnect_all Disconnect all sessions
|
|
761
|
+
:switch <device> Switch to another active session
|
|
604
762
|
:sessions List all active sessions
|
|
605
763
|
|
|
606
764
|
Quick Commands (platform-aware, auto-selects correct syntax):
|
|
@@ -610,7 +768,7 @@ Quick Commands (platform-aware, auto-selects correct syntax):
|
|
|
610
768
|
:neighbors Fetch CDP/LLDP neighbors (tries both)
|
|
611
769
|
:bgp Fetch BGP summary
|
|
612
770
|
:routes Fetch routing table
|
|
613
|
-
:intf <
|
|
771
|
+
:intf <n> Fetch specific interface details
|
|
614
772
|
|
|
615
773
|
Settings:
|
|
616
774
|
:policy [mode] Get/set policy mode (read_only|ops)
|
|
@@ -628,15 +786,16 @@ Info:
|
|
|
628
786
|
Raw Commands:
|
|
629
787
|
(anything else) Sends as CLI command to connected device
|
|
630
788
|
|
|
631
|
-
|
|
632
|
-
:
|
|
633
|
-
|
|
634
|
-
:connect
|
|
635
|
-
|
|
636
|
-
:
|
|
637
|
-
:
|
|
638
|
-
show ip route
|
|
639
|
-
:disconnect
|
|
789
|
+
Multi-Session Example:
|
|
790
|
+
:connect spine-1 # Connect to first device
|
|
791
|
+
show version # Run command on spine-1
|
|
792
|
+
:connect spine-2 # Connect to second (spine-1 stays active!)
|
|
793
|
+
show version # Run command on spine-2
|
|
794
|
+
:sessions # See both sessions
|
|
795
|
+
:switch spine-1 # Switch back to spine-1
|
|
796
|
+
show ip route # Run command on spine-1
|
|
797
|
+
:disconnect # Disconnect spine-1, auto-switch to spine-2
|
|
798
|
+
:disconnect_all # Disconnect all remaining sessions
|
|
640
799
|
:exit
|
|
641
800
|
"""
|
|
642
801
|
|
|
@@ -200,9 +200,34 @@ def _display_result(repl: NTermREPL, result: Dict) -> None:
|
|
|
200
200
|
print(f" Prompt: {data.get('prompt', '')}")
|
|
201
201
|
return
|
|
202
202
|
|
|
203
|
+
if cmd_type == "switch":
|
|
204
|
+
msg = data.get('message', '')
|
|
205
|
+
if msg:
|
|
206
|
+
print(f"✓ {msg}")
|
|
207
|
+
else:
|
|
208
|
+
print(f"✓ Switched to {data['device']} ({data['hostname']}:{data['port']})")
|
|
209
|
+
print(f" Platform: {data.get('platform', 'unknown')}")
|
|
210
|
+
print(f" Prompt: {data.get('prompt', '')}")
|
|
211
|
+
return
|
|
212
|
+
|
|
203
213
|
if cmd_type == "disconnect":
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
disconnected = data.get("disconnected")
|
|
215
|
+
switched_to = data.get("switched_to")
|
|
216
|
+
msg = data.get("message")
|
|
217
|
+
|
|
218
|
+
if msg:
|
|
219
|
+
print(f"✓ {msg}")
|
|
220
|
+
elif disconnected:
|
|
221
|
+
print(f"✓ Disconnected from {disconnected}")
|
|
222
|
+
if switched_to:
|
|
223
|
+
print(f" Switched to: {switched_to}")
|
|
224
|
+
else:
|
|
225
|
+
print("✓ Disconnected")
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
if cmd_type == "disconnect_all":
|
|
229
|
+
count = data.get("count", 0)
|
|
230
|
+
print(f"✓ Disconnected {count} session(s)")
|
|
206
231
|
return
|
|
207
232
|
|
|
208
233
|
if cmd_type == "sessions":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ntermqt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
@@ -112,7 +112,7 @@ nterm includes a built-in development console accessible via **Dev → IPython**
|
|
|
112
112
|
|
|
113
113
|

|
|
114
114
|
|
|
115
|
-

|
|
116
116
|
|
|
117
117
|
The IPython console runs in the same Python environment as nterm, with the scripting API pre-loaded. Query your device inventory, inspect credentials, and prototype automation workflows without leaving the app.
|
|
118
118
|
|
|
@@ -6,10 +6,11 @@ nterm/askpass/__init__.py,sha256=UpJBk0EOm0nkRwMVv7YdIB4v75ZJpSYmNsU_GlgzbUg,495
|
|
|
6
6
|
nterm/askpass/server.py,sha256=5tvjYryyfu-n8Cw2KbucwaZfWiqYnFk-iBAVBI8FMfw,12873
|
|
7
7
|
nterm/connection/__init__.py,sha256=2qQ9LGxUxmwem8deOD2WZVkeD6rIVlTlx5Zh2cUEmxY,261
|
|
8
8
|
nterm/connection/profile.py,sha256=4RMgnRNKCc-dFGEIpmQc_bob5MtzxO04_PljP-qUGLs,9450
|
|
9
|
+
nterm/examples/basic_terminal.py,sha256=vbDI1xl-Radv6GYZ0yC6QUafQp_tSX2pWIf7tk58W8E,15256
|
|
9
10
|
nterm/manager/__init__.py,sha256=_QIeTap5CTL3jdTS1Q16fAt-PrqcNPUVr9gtJ22f0ng,774
|
|
10
11
|
nterm/manager/connect_dialog.py,sha256=yd8g_gYttT_UdflRxSfyss8OQTfrvKLUOMg4Kj8FPNo,11711
|
|
11
12
|
nterm/manager/editor.py,sha256=Fn2YWHJ1EwPYrhKhsi4GTBYwRfCYsHsqgKkLY-LQ8JI,8469
|
|
12
|
-
nterm/manager/io.py,sha256=
|
|
13
|
+
nterm/manager/io.py,sha256=59ehTfnS1sAKEEEwMxujHuccV7rYSoRx4vfS1ExDqW4,32572
|
|
13
14
|
nterm/manager/models.py,sha256=cvC2HzCRadNG1EYsnZN4C9YS6uolHGcUGGZtt-wzGF4,12237
|
|
14
15
|
nterm/manager/settings.py,sha256=r6MTw_9r1Wl2UX_ALpXIuPbDvJ0D91Y8wRKq6Bfr_3g,9210
|
|
15
16
|
nterm/manager/tree.py,sha256=I78wSjkSuyM6903II-XNyPug9saMSODUNBCHCDrq4ls,22397
|
|
@@ -20,13 +21,13 @@ nterm/parser/tfsm_engine.py,sha256=6p4wrNa9tQRuCmWgsR4E3rZTprpLmii5PNjoGpCQBCw,7
|
|
|
20
21
|
nterm/parser/tfsm_fire.py,sha256=AHbN6p4HlgcYDjLWb67CF9YfMSTk-3aetMswmEZyRVc,9222
|
|
21
22
|
nterm/parser/tfsm_fire_tester.py,sha256=h2CAqTS6ZNHMUr4di2DBRHAWbBGiUTliOvm5jVG4ltI,79146
|
|
22
23
|
nterm/scripting/__init__.py,sha256=vxbODaXR0IPneja3BuDHmjsHzQg03tFWtHO4Rc6vCTk,1099
|
|
23
|
-
nterm/scripting/api.py,sha256=
|
|
24
|
+
nterm/scripting/api.py,sha256=nWuANeYyT8mI1OsP9g3Z2lZohrrCpAZVss6t9Ba5Avg,39011
|
|
24
25
|
nterm/scripting/cli.py,sha256=W2DK4ZnuutaArye_to7CBchg0ogClURxVbGsMdnj1y0,9187
|
|
25
26
|
nterm/scripting/models.py,sha256=zX90xtFYz0fqIPc0G8mRaoiZ1aRLm0koiHIvklUBflg,6858
|
|
26
27
|
nterm/scripting/platform_data.py,sha256=uCWBDS1HqmOdYNPojNlKBWLYhLiME9eoE0LWL-w6dkQ,10200
|
|
27
|
-
nterm/scripting/platform_utils.py,sha256=
|
|
28
|
-
nterm/scripting/repl.py,sha256=
|
|
29
|
-
nterm/scripting/repl_interactive.py,sha256=
|
|
28
|
+
nterm/scripting/platform_utils.py,sha256=_vb1tMstlD9pQ4blD2keAkVbs9lkbFlTBEavI5oMd3Q,18478
|
|
29
|
+
nterm/scripting/repl.py,sha256=URcgs-c-bEYsw2JNCewEonNy1oLsGpzsBcq5xpYWseM,29152
|
|
30
|
+
nterm/scripting/repl_interactive.py,sha256=yQ-XjyELBCE5t4Gk3hBnO8eZCbCXquddLBqcQIKPhY8,17870
|
|
30
31
|
nterm/scripting/ssh_connection.py,sha256=p9EGPE3hgbceDVh7UdGz44cSi79Vl7g9Q4fKd-2T314,19270
|
|
31
32
|
nterm/scripting/test_api_repl.py,sha256=deeA_epPnBxgDGYMKcyXEWQOOpIXQNjDZL01FmOpKvs,8762
|
|
32
33
|
nterm/session/__init__.py,sha256=FkgHF1WPz78JBOWHSC7LLynG2NqoR6aanNTRlEzsO6I,1612
|
|
@@ -66,8 +67,8 @@ nterm/vault/manager_ui.py,sha256=qle-W40j6L_pOR0AaOCeyU8myizFTRkISNrloCn0H_Y,345
|
|
|
66
67
|
nterm/vault/profile.py,sha256=qM9TJf68RKdjtxo-sJehO7wS4iTi2G26BKbmlmHLA5M,6246
|
|
67
68
|
nterm/vault/resolver.py,sha256=GWB2YR9H1MH98RGQBKvitIsjWT_-wSMLuddZNz4wbns,7800
|
|
68
69
|
nterm/vault/store.py,sha256=_0Lfe0WKjm3uSAtxgn9qAPlpBOLCuq9SVgzqsE_qaGQ,21199
|
|
69
|
-
ntermqt-0.1.
|
|
70
|
-
ntermqt-0.1.
|
|
71
|
-
ntermqt-0.1.
|
|
72
|
-
ntermqt-0.1.
|
|
73
|
-
ntermqt-0.1.
|
|
70
|
+
ntermqt-0.1.10.dist-info/METADATA,sha256=Pa4Oytg8jq0W5pU0bmmJR9nryjKRwewy54qejJValjM,16041
|
|
71
|
+
ntermqt-0.1.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
72
|
+
ntermqt-0.1.10.dist-info/entry_points.txt,sha256=Gunr-_3w-aSpfqoMuGKM2PJSCRo9hZ7K1BksUtp1yd8,130
|
|
73
|
+
ntermqt-0.1.10.dist-info/top_level.txt,sha256=bZdnNLTHNRNqo9jsOQGUWF7h5st0xW_thH0n2QOxWUo,6
|
|
74
|
+
ntermqt-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|