meshcore-cli 1.3.11__py3-none-any.whl → 1.3.13__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.
- meshcore_cli/meshcore_cli.py +311 -65
- {meshcore_cli-1.3.11.dist-info → meshcore_cli-1.3.13.dist-info}/METADATA +13 -1
- meshcore_cli-1.3.13.dist-info/RECORD +8 -0
- meshcore_cli-1.3.11.dist-info/RECORD +0 -8
- {meshcore_cli-1.3.11.dist-info → meshcore_cli-1.3.13.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.3.11.dist-info → meshcore_cli-1.3.13.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.3.11.dist-info → meshcore_cli-1.3.13.dist-info}/licenses/LICENSE +0 -0
meshcore_cli/meshcore_cli.py
CHANGED
|
@@ -32,7 +32,7 @@ import re
|
|
|
32
32
|
from meshcore import MeshCore, EventType, logger
|
|
33
33
|
|
|
34
34
|
# Version
|
|
35
|
-
VERSION = "v1.3.
|
|
35
|
+
VERSION = "v1.3.12"
|
|
36
36
|
|
|
37
37
|
# default ble address is stored in a config file
|
|
38
38
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -72,6 +72,8 @@ ANSI_LIGHT_GREEN = "\033[0;92m"
|
|
|
72
72
|
ANSI_LIGHT_YELLOW = "\033[0;93m"
|
|
73
73
|
ANSI_LIGHT_GRAY="\033[0;38;5;247m"
|
|
74
74
|
ANSI_BGRAY="\033[1;38;5;247m"
|
|
75
|
+
ANSI_GRAY_BACK="\033[48;5;247m"
|
|
76
|
+
ANSI_RESET_BACK="\033[49m"
|
|
75
77
|
ANSI_ORANGE="\033[0;38;5;214m"
|
|
76
78
|
ANSI_BORANGE="\033[1;38;5;214m"
|
|
77
79
|
#ANSI_YELLOW="\033[0;38;5;226m"
|
|
@@ -82,8 +84,9 @@ ANSI_BYELLOW = "\033[1;33m"
|
|
|
82
84
|
#Unicode chars
|
|
83
85
|
# some possible symbols for prompts 🭬🬛🬗🭬🬛🬃🬗🭬🬛🬃🬗🬏🭀🭋🭨🮋
|
|
84
86
|
ARROW_HEAD = ""
|
|
85
|
-
SLASH_END = ""
|
|
86
|
-
SLASH_START = ""
|
|
87
|
+
SLASH_END = f"{ANSI_RESET_BACK}"
|
|
88
|
+
#SLASH_START = ""
|
|
89
|
+
SLASH_START = f"{ANSI_GRAY_BACK}"
|
|
87
90
|
INVERT_SLASH = False
|
|
88
91
|
|
|
89
92
|
def escape_ansi(line):
|
|
@@ -294,7 +297,7 @@ async def handle_log_rx(event):
|
|
|
294
297
|
chan_name = channel["channel_name"]
|
|
295
298
|
aes_key = bytes.fromhex(channel["channel_secret"])
|
|
296
299
|
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
297
|
-
message = cipher.decrypt(msg)[5:].decode("utf-8").strip("\x00")
|
|
300
|
+
message = cipher.decrypt(msg)[5:].decode("utf-8", "ignore").strip("\x00")
|
|
298
301
|
|
|
299
302
|
if chan_name != "" :
|
|
300
303
|
width = os.get_terminal_size().columns
|
|
@@ -324,7 +327,7 @@ async def handle_log_rx(event):
|
|
|
324
327
|
if flags & 0x40 > 0: #has feature2
|
|
325
328
|
adv_feat2 = pk_buf.read(2).hex()
|
|
326
329
|
if flags & 0x80 > 0: #has name
|
|
327
|
-
adv_name = pk_buf.read().decode("utf-8").strip("\x00")
|
|
330
|
+
adv_name = pk_buf.read().decode("utf-8", "ignore").strip("\x00")
|
|
328
331
|
|
|
329
332
|
if adv_name is None:
|
|
330
333
|
# try to get the name from the contact
|
|
@@ -443,10 +446,16 @@ async def log_message(mc, msg):
|
|
|
443
446
|
ct = mc.get_contact_by_key_prefix(msg['pubkey_prefix'])
|
|
444
447
|
if ct is None:
|
|
445
448
|
msg["name"] = msg["pubkey_prefix"]
|
|
449
|
+
msg["sender"] = msg["pubkey_prefix"]
|
|
446
450
|
else:
|
|
447
451
|
msg["name"] = ct["adv_name"]
|
|
452
|
+
msg["sender"] = ct["adv_name"]
|
|
448
453
|
elif msg["type"] == "CHAN" :
|
|
449
|
-
|
|
454
|
+
if hasattr(mc, 'channels') :
|
|
455
|
+
msg["sender"] = mc.channels[msg['channel_idx']]["channel_name"]
|
|
456
|
+
else:
|
|
457
|
+
msg["sender"] = f"channel {msg['channel_idx']}"
|
|
458
|
+
msg["name"] = msg["sender"]
|
|
450
459
|
msg["timestamp"] = int(time.time())
|
|
451
460
|
|
|
452
461
|
with open(log_message.file, "a") as logfile:
|
|
@@ -1019,7 +1028,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1019
1028
|
if '%' in dest and scope!=None :
|
|
1020
1029
|
dest_scope = dest.split("%")[-1]
|
|
1021
1030
|
dest = dest[:-len(dest_scope)-1]
|
|
1022
|
-
nc = mc
|
|
1031
|
+
nc = await get_contact_from_arg(mc, dest)
|
|
1023
1032
|
if nc is None:
|
|
1024
1033
|
if dest == "public" :
|
|
1025
1034
|
nc = {"adv_name" : "public", "type" : 0, "chan_nb" : 0}
|
|
@@ -1074,7 +1083,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1074
1083
|
dest_scope = dest.split("%")[-1]
|
|
1075
1084
|
dest = dest[:-len(dest_scope)-1]
|
|
1076
1085
|
await set_scope (mc, dest_scope)
|
|
1077
|
-
tct = mc
|
|
1086
|
+
tct = get_contact_from_arg(mc, dest)
|
|
1078
1087
|
if len(args)>1 and not tct is None: # a contact, send a message
|
|
1079
1088
|
if tct["type"] == 1 or tct["type"] == 3: # client or room
|
|
1080
1089
|
last_ack = await msg_ack(mc, tct, line.split(" ", 1)[1])
|
|
@@ -1097,7 +1106,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1097
1106
|
dest_scope = contact_name.split("%")[-1]
|
|
1098
1107
|
contact_name = contact_name[:-len(dest_scope)-1]
|
|
1099
1108
|
await set_scope (mc, dest_scope)
|
|
1100
|
-
tct = mc.
|
|
1109
|
+
tct = mc.get_contact_from_arg(mc, contact_name)
|
|
1101
1110
|
if tct is None:
|
|
1102
1111
|
print(f"{contact_name} is not a contact")
|
|
1103
1112
|
else:
|
|
@@ -1337,9 +1346,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1337
1346
|
perm = int(perm_string[1:])
|
|
1338
1347
|
else:
|
|
1339
1348
|
perm = int(perm_string,16)
|
|
1340
|
-
ct=mc
|
|
1341
|
-
if ct is None:
|
|
1342
|
-
ct=mc.get_contact_by_key_prefix(name)
|
|
1349
|
+
ct= await get_contact_from_arg(mc, name)
|
|
1343
1350
|
if ct is None:
|
|
1344
1351
|
if name == "self" or mc.self_info["public_key"].startswith(name):
|
|
1345
1352
|
key = mc.self_info["public_key"]
|
|
@@ -1538,10 +1545,11 @@ async def send_cmd (mc, contact, cmd) :
|
|
|
1538
1545
|
if isinstance(contact, dict):
|
|
1539
1546
|
sent = res.payload.copy()
|
|
1540
1547
|
sent["type"] = "SENT_CMD"
|
|
1541
|
-
sent["
|
|
1548
|
+
sent["recipient"] = contact["adv_name"]
|
|
1542
1549
|
sent["text"] = cmd
|
|
1543
1550
|
sent["txt_type"] = 1
|
|
1544
|
-
sent["
|
|
1551
|
+
sent["sender"] = mc.self_info['name']
|
|
1552
|
+
sent["name"] = sent["recipient"]
|
|
1545
1553
|
await log_message(mc, sent)
|
|
1546
1554
|
return res
|
|
1547
1555
|
|
|
@@ -1551,9 +1559,16 @@ async def send_chan_msg(mc, nb, msg):
|
|
|
1551
1559
|
sent = res.payload.copy()
|
|
1552
1560
|
sent["type"] = "SENT_CHAN"
|
|
1553
1561
|
sent["channel_idx"] = nb
|
|
1562
|
+
if hasattr(mc, "channels"):
|
|
1563
|
+
chan_name = mc.channels[nb]["channel_name"]
|
|
1564
|
+
else:
|
|
1565
|
+
chan_name = f"channel {nb}"
|
|
1566
|
+
sent["chan_name"] = chan_name
|
|
1567
|
+
sent["recipient"] = chan_name
|
|
1554
1568
|
sent["text"] = msg
|
|
1555
1569
|
sent["txt_type"] = 0
|
|
1556
|
-
sent["
|
|
1570
|
+
sent["sender"] = mc.self_info['name']
|
|
1571
|
+
sent["name"] = chan_name
|
|
1557
1572
|
await log_message(mc, sent)
|
|
1558
1573
|
return res
|
|
1559
1574
|
|
|
@@ -1564,10 +1579,11 @@ async def send_msg (mc, contact, msg) :
|
|
|
1564
1579
|
if isinstance(contact, dict):
|
|
1565
1580
|
sent = res.payload.copy()
|
|
1566
1581
|
sent["type"] = "SENT_MSG"
|
|
1567
|
-
sent["
|
|
1582
|
+
sent["recipient"] = contact["adv_name"]
|
|
1568
1583
|
sent["text"] = msg
|
|
1569
1584
|
sent["txt_type"] = 0
|
|
1570
|
-
sent["
|
|
1585
|
+
sent["sender"] = mc.self_info['name']
|
|
1586
|
+
sent["name"] = sent["recipient"]
|
|
1571
1587
|
await log_message(mc, sent)
|
|
1572
1588
|
return res
|
|
1573
1589
|
|
|
@@ -1584,10 +1600,11 @@ async def msg_ack (mc, contact, msg) :
|
|
|
1584
1600
|
if isinstance(contact, dict):
|
|
1585
1601
|
sent = res.payload.copy()
|
|
1586
1602
|
sent["type"] = "SENT_MSG"
|
|
1587
|
-
sent["
|
|
1603
|
+
sent["recipient"] = contact["adv_name"]
|
|
1588
1604
|
sent["text"] = msg
|
|
1589
1605
|
sent["txt_type"] = 0
|
|
1590
|
-
sent["
|
|
1606
|
+
sent["sender"] = mc.self_info['name']
|
|
1607
|
+
sent["name"] = sent["recipient"]
|
|
1591
1608
|
await log_message(mc, sent)
|
|
1592
1609
|
return not res is None
|
|
1593
1610
|
msg_ack.max_attempts=3
|
|
@@ -1828,6 +1845,22 @@ async def print_disc_trace_to (mc, contact):
|
|
|
1828
1845
|
|
|
1829
1846
|
await next_cmd(mc, ["trace", trace])
|
|
1830
1847
|
|
|
1848
|
+
|
|
1849
|
+
async def get_contact_from_arg(mc, arg):
|
|
1850
|
+
contact = None
|
|
1851
|
+
await mc.ensure_contacts()
|
|
1852
|
+
|
|
1853
|
+
# first try with key prefix
|
|
1854
|
+
try: # try only if its a valid hex
|
|
1855
|
+
int(arg ,16)
|
|
1856
|
+
contact = mc.get_contact_by_key_prefix(arg)
|
|
1857
|
+
except ValueError:
|
|
1858
|
+
pass
|
|
1859
|
+
if contact is None: # try by name
|
|
1860
|
+
contact = mc.get_contact_by_name(arg)
|
|
1861
|
+
|
|
1862
|
+
return contact
|
|
1863
|
+
|
|
1831
1864
|
async def next_cmd(mc, cmds, json_output=False):
|
|
1832
1865
|
""" process next command """
|
|
1833
1866
|
global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
|
|
@@ -2478,8 +2511,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2478
2511
|
dest = None
|
|
2479
2512
|
|
|
2480
2513
|
if dest is None:
|
|
2481
|
-
await mc
|
|
2482
|
-
dest = mc.get_contact_by_name(cmds[1])
|
|
2514
|
+
dest = await get_contact_from_arg(mc, cmds[1])
|
|
2483
2515
|
|
|
2484
2516
|
if dest is None:
|
|
2485
2517
|
if json_output :
|
|
@@ -2500,7 +2532,9 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2500
2532
|
if cmds[1].isnumeric() :
|
|
2501
2533
|
nb = int(cmds[1])
|
|
2502
2534
|
else:
|
|
2503
|
-
|
|
2535
|
+
chan = await get_channel_by_name(mc, cmds[1])
|
|
2536
|
+
print (chan)
|
|
2537
|
+
nb = chan['channel_idx']
|
|
2504
2538
|
res = await send_chan_msg(mc, nb, cmds[2])
|
|
2505
2539
|
logger.debug(res)
|
|
2506
2540
|
if res.type == EventType.ERROR:
|
|
@@ -2528,8 +2562,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2528
2562
|
dest = None
|
|
2529
2563
|
|
|
2530
2564
|
if dest is None:
|
|
2531
|
-
await mc
|
|
2532
|
-
dest = mc.get_contact_by_name(cmds[1])
|
|
2565
|
+
dest = await get_contact_from_arg(mc, cmds[1])
|
|
2533
2566
|
|
|
2534
2567
|
if dest is None:
|
|
2535
2568
|
if json_output :
|
|
@@ -2606,9 +2639,9 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2606
2639
|
|
|
2607
2640
|
case "login" | "l" :
|
|
2608
2641
|
argnum = 2
|
|
2609
|
-
await mc
|
|
2610
|
-
|
|
2611
|
-
if contact is None:
|
|
2642
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2643
|
+
|
|
2644
|
+
if contact is None: # still none ? contact not found
|
|
2612
2645
|
if json_output :
|
|
2613
2646
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
2614
2647
|
else:
|
|
@@ -2645,8 +2678,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2645
2678
|
|
|
2646
2679
|
case "logout" :
|
|
2647
2680
|
argnum = 1
|
|
2648
|
-
await mc
|
|
2649
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2681
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2650
2682
|
res = await mc.commands.send_logout(contact)
|
|
2651
2683
|
logger.debug(res)
|
|
2652
2684
|
if res.type == EventType.ERROR:
|
|
@@ -2658,14 +2690,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2658
2690
|
|
|
2659
2691
|
case "contact_timeout" :
|
|
2660
2692
|
argnum = 2
|
|
2661
|
-
await mc
|
|
2662
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2693
|
+
contact = await get_contact_from_args(mc, cmds[1])
|
|
2663
2694
|
contact["timeout"] = float(cmds[2])
|
|
2664
2695
|
|
|
2665
2696
|
case "disc_path" | "dp" :
|
|
2666
2697
|
argnum = 1
|
|
2667
|
-
await mc
|
|
2668
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2698
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2669
2699
|
res = await discover_path(mc, contact)
|
|
2670
2700
|
if res is None:
|
|
2671
2701
|
print(f"Error while discovering path")
|
|
@@ -2746,7 +2776,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2746
2776
|
case "req_telemetry"|"rt" :
|
|
2747
2777
|
argnum = 1
|
|
2748
2778
|
await mc.ensure_contacts()
|
|
2749
|
-
contact = mc
|
|
2779
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2750
2780
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2751
2781
|
res = await mc.commands.req_telemetry_sync(contact, timeout)
|
|
2752
2782
|
if res is None :
|
|
@@ -2763,8 +2793,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2763
2793
|
|
|
2764
2794
|
case "req_status"|"rs" :
|
|
2765
2795
|
argnum = 1
|
|
2766
|
-
await mc
|
|
2767
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2796
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2768
2797
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2769
2798
|
res = await mc.commands.req_status_sync(contact, timeout)
|
|
2770
2799
|
if res is None :
|
|
@@ -2778,7 +2807,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2778
2807
|
case "req_mma" | "rm":
|
|
2779
2808
|
argnum = 3
|
|
2780
2809
|
await mc.ensure_contacts()
|
|
2781
|
-
contact = mc
|
|
2810
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2782
2811
|
if cmds[2][-1] == "s":
|
|
2783
2812
|
from_secs = int(cmds[2][0:-1])
|
|
2784
2813
|
elif cmds[2][-1] == "m":
|
|
@@ -2807,8 +2836,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2807
2836
|
|
|
2808
2837
|
case "req_acl" :
|
|
2809
2838
|
argnum = 1
|
|
2810
|
-
await mc
|
|
2811
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2839
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2812
2840
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2813
2841
|
res = await mc.commands.req_acl_sync(contact, timeout)
|
|
2814
2842
|
if res is None :
|
|
@@ -2832,8 +2860,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2832
2860
|
|
|
2833
2861
|
case "req_neighbours"|"rn" :
|
|
2834
2862
|
argnum = 1
|
|
2835
|
-
await mc
|
|
2836
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2863
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2837
2864
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2838
2865
|
res = await mc.commands.fetch_all_neighbours(contact, timeout=timeout)
|
|
2839
2866
|
if res is None :
|
|
@@ -2872,8 +2899,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2872
2899
|
|
|
2873
2900
|
case "req_binary" :
|
|
2874
2901
|
argnum = 2
|
|
2875
|
-
await mc
|
|
2876
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2902
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2877
2903
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2878
2904
|
res = await mc.commands.req_binary(contact, bytes.fromhex(cmds[2]), timeout)
|
|
2879
2905
|
if res is None :
|
|
@@ -2947,8 +2973,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2947
2973
|
|
|
2948
2974
|
case "path":
|
|
2949
2975
|
argnum = 1
|
|
2950
|
-
|
|
2951
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2976
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2952
2977
|
if contact is None:
|
|
2953
2978
|
if json_output :
|
|
2954
2979
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -2972,7 +2997,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2972
2997
|
case "contact_info" | "ci":
|
|
2973
2998
|
argnum = 1
|
|
2974
2999
|
res = await mc.ensure_contacts(follow=True)
|
|
2975
|
-
contact = mc
|
|
3000
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2976
3001
|
if contact is None:
|
|
2977
3002
|
if json_output :
|
|
2978
3003
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -2983,8 +3008,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2983
3008
|
|
|
2984
3009
|
case "change_path" | "cp":
|
|
2985
3010
|
argnum = 2
|
|
2986
|
-
await mc
|
|
2987
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3011
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2988
3012
|
if contact is None:
|
|
2989
3013
|
if json_output :
|
|
2990
3014
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3006,8 +3030,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3006
3030
|
|
|
3007
3031
|
case "change_flags" | "cf":
|
|
3008
3032
|
argnum = 2
|
|
3009
|
-
await mc
|
|
3010
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3033
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3011
3034
|
if contact is None:
|
|
3012
3035
|
if json_output :
|
|
3013
3036
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3023,8 +3046,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3023
3046
|
|
|
3024
3047
|
case "reset_path" | "rp" :
|
|
3025
3048
|
argnum = 1
|
|
3026
|
-
await mc
|
|
3027
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3049
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3028
3050
|
if contact is None:
|
|
3029
3051
|
if json_output :
|
|
3030
3052
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3043,8 +3065,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3043
3065
|
|
|
3044
3066
|
case "share_contact" | "sc":
|
|
3045
3067
|
argnum = 1
|
|
3046
|
-
await mc
|
|
3047
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3068
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3048
3069
|
if contact is None:
|
|
3049
3070
|
if json_output :
|
|
3050
3071
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3060,8 +3081,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3060
3081
|
|
|
3061
3082
|
case "export_contact"|"ec":
|
|
3062
3083
|
argnum = 1
|
|
3063
|
-
await mc
|
|
3064
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3084
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3065
3085
|
if contact is None:
|
|
3066
3086
|
if json_output :
|
|
3067
3087
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3091,8 +3111,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3091
3111
|
|
|
3092
3112
|
case "upload_contact" | "uc" :
|
|
3093
3113
|
argnum = 1
|
|
3094
|
-
await mc
|
|
3095
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3114
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3096
3115
|
if contact is None:
|
|
3097
3116
|
if json_output :
|
|
3098
3117
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3136,7 +3155,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3136
3155
|
|
|
3137
3156
|
case "remove_contact" :
|
|
3138
3157
|
argnum = 1
|
|
3139
|
-
await mc.ensure_contacts()
|
|
3140
3158
|
contact = mc.get_contact_by_name(cmds[1])
|
|
3141
3159
|
if contact is None:
|
|
3142
3160
|
if json_output :
|
|
@@ -3258,8 +3276,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3258
3276
|
|
|
3259
3277
|
case "chat_to" | "imto" | "to" :
|
|
3260
3278
|
argnum = 1
|
|
3261
|
-
await mc
|
|
3262
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3279
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3263
3280
|
await interactive_loop(mc, to=contact)
|
|
3264
3281
|
|
|
3265
3282
|
case "script" :
|
|
@@ -3267,8 +3284,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3267
3284
|
await process_script(mc, cmds[1], json_output=json_output)
|
|
3268
3285
|
|
|
3269
3286
|
case _ :
|
|
3270
|
-
await mc
|
|
3271
|
-
contact = mc.get_contact_by_name(cmds[0])
|
|
3287
|
+
contact = await get_contact_from_arg(mc, cmds[0])
|
|
3272
3288
|
if contact is None:
|
|
3273
3289
|
logger.error(f"Unknown command : {cmd}, {cmds} not executed ...")
|
|
3274
3290
|
return None
|
|
@@ -3408,6 +3424,7 @@ def command_usage() :
|
|
|
3408
3424
|
-b <baudrate> : specify baudrate
|
|
3409
3425
|
-C : toggles classic mode for prompt
|
|
3410
3426
|
-c <on/off> : disables most of color output if off
|
|
3427
|
+
-r : repeater mode (raw text CLI, use with -s)
|
|
3411
3428
|
""")
|
|
3412
3429
|
|
|
3413
3430
|
def get_help_for (cmdname, context="line") :
|
|
@@ -3494,7 +3511,7 @@ def get_help_for (cmdname, context="line") :
|
|
|
3494
3511
|
json_log_rx <on/off> : logs packets incoming to device as json
|
|
3495
3512
|
channel_echoes <on/off> : print repeats for channel data
|
|
3496
3513
|
advert_echoes <on/off> : print repeats for adverts
|
|
3497
|
-
|
|
3514
|
+
echo_unk_chans <on/off> : also dump unk channels (encrypted)
|
|
3498
3515
|
color <on/off> : color off should remove ANSI codes from output
|
|
3499
3516
|
meshcore-cli behaviour:
|
|
3500
3517
|
classic_prompt <on/off> : activates less fancier prompt
|
|
@@ -3567,6 +3584,223 @@ To remove a channel, use remove_channel, either with channel name or number.
|
|
|
3567
3584
|
else:
|
|
3568
3585
|
print(f"Sorry, no help yet for {cmdname}")
|
|
3569
3586
|
|
|
3587
|
+
# Repeater mode history file
|
|
3588
|
+
MCCLI_REPEATER_HISTORY_FILE = MCCLI_CONFIG_DIR + "repeater_history"
|
|
3589
|
+
|
|
3590
|
+
# Repeater command completion dictionary
|
|
3591
|
+
REPEATER_COMMANDS = {
|
|
3592
|
+
"ver": None,
|
|
3593
|
+
"board": None,
|
|
3594
|
+
"reboot": None,
|
|
3595
|
+
"advert": None,
|
|
3596
|
+
"clock": {"sync": None},
|
|
3597
|
+
"time": None,
|
|
3598
|
+
"neighbors": None,
|
|
3599
|
+
"stats-core": None,
|
|
3600
|
+
"stats-radio": None,
|
|
3601
|
+
"stats-packets": None,
|
|
3602
|
+
"clear": {"stats": None},
|
|
3603
|
+
"log": {"start": None, "stop": None, "erase": None},
|
|
3604
|
+
"get": {
|
|
3605
|
+
"name": None, "radio": None, "tx": None, "freq": None,
|
|
3606
|
+
"public.key": None, "prv.key": None, "repeat": None, "role": None,
|
|
3607
|
+
"lat": None, "lon": None, "af": None,
|
|
3608
|
+
"rxdelay": None, "txdelay": None, "direct.txdelay": None,
|
|
3609
|
+
"flood.max": None, "flood.advert.interval": None,
|
|
3610
|
+
"advert.interval": None, "guest.password": None,
|
|
3611
|
+
"allow.read.only": None, "multi.acks": None,
|
|
3612
|
+
"int.thresh": None, "agc.reset.interval": None,
|
|
3613
|
+
"bridge.enabled": None, "bridge.delay": None,
|
|
3614
|
+
"bridge.source": None, "bridge.baud": None,
|
|
3615
|
+
"bridge.channel": None, "bridge.secret": None, "bridge.type": None,
|
|
3616
|
+
"adc.multiplier": None, "acl": None,
|
|
3617
|
+
},
|
|
3618
|
+
"set": {
|
|
3619
|
+
"name": None, "radio": None, "tx": None, "freq": None,
|
|
3620
|
+
"prv.key": None, "repeat": {"on": None, "off": None},
|
|
3621
|
+
"lat": None, "lon": None, "af": None,
|
|
3622
|
+
"rxdelay": None, "txdelay": None, "direct.txdelay": None,
|
|
3623
|
+
"flood.max": None, "flood.advert.interval": None,
|
|
3624
|
+
"advert.interval": None, "guest.password": None,
|
|
3625
|
+
"allow.read.only": {"on": None, "off": None},
|
|
3626
|
+
"multi.acks": None, "int.thresh": None, "agc.reset.interval": None,
|
|
3627
|
+
"bridge.enabled": {"on": None, "off": None},
|
|
3628
|
+
"bridge.delay": None, "bridge.source": None,
|
|
3629
|
+
"bridge.baud": None, "bridge.channel": None, "bridge.secret": None,
|
|
3630
|
+
"adc.multiplier": None,
|
|
3631
|
+
},
|
|
3632
|
+
"password": None,
|
|
3633
|
+
"erase": None,
|
|
3634
|
+
"gps": {"on": None, "off": None, "sync": None, "setloc": None, "advert": {"none": None, "share": None, "prefs": None}},
|
|
3635
|
+
"sensor": {"list": None, "get": None, "set": None},
|
|
3636
|
+
"region": {"get": None, "put": None, "remove": None, "save": None, "load": None, "home": None, "allowf": None, "denyf": None},
|
|
3637
|
+
"setperm": None,
|
|
3638
|
+
"tempradio": None,
|
|
3639
|
+
"neighbor.remove": None,
|
|
3640
|
+
"quit": None,
|
|
3641
|
+
"q": None,
|
|
3642
|
+
"help": None,
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
REPEATER_HELP = f"""
|
|
3646
|
+
{ANSI_BCYAN}Repeater CLI Commands:{ANSI_END}
|
|
3647
|
+
|
|
3648
|
+
{ANSI_BGREEN}Info:{ANSI_END}
|
|
3649
|
+
ver - Firmware version
|
|
3650
|
+
board - Board name
|
|
3651
|
+
clock - Show current time
|
|
3652
|
+
|
|
3653
|
+
{ANSI_BGREEN}Stats:{ANSI_END}
|
|
3654
|
+
stats-core - Core stats (uptime, battery, queue)
|
|
3655
|
+
stats-radio - Radio stats (RSSI, SNR, noise floor)
|
|
3656
|
+
stats-packets - Packet statistics (sent/recv counts)
|
|
3657
|
+
clear stats - Reset all statistics
|
|
3658
|
+
|
|
3659
|
+
{ANSI_BGREEN}Network:{ANSI_END}
|
|
3660
|
+
neighbors - Show neighboring repeaters (zero-hop)
|
|
3661
|
+
advert - Send advertisement now
|
|
3662
|
+
|
|
3663
|
+
{ANSI_BGREEN}Logging:{ANSI_END}
|
|
3664
|
+
log start - Enable packet logging
|
|
3665
|
+
log stop - Disable packet logging
|
|
3666
|
+
log - Dump log file to console
|
|
3667
|
+
log erase - Erase log file
|
|
3668
|
+
|
|
3669
|
+
{ANSI_BGREEN}Configuration (get/set):{ANSI_END}
|
|
3670
|
+
get name - Node name
|
|
3671
|
+
get radio - Radio params (freq,bw,sf,cr)
|
|
3672
|
+
get tx - TX power (dBm)
|
|
3673
|
+
get repeat - Repeat mode on/off
|
|
3674
|
+
get public.key - Node public key
|
|
3675
|
+
get advert.interval - Advertisement interval (minutes)
|
|
3676
|
+
|
|
3677
|
+
set name <name> - Set node name
|
|
3678
|
+
set tx <power> - Set TX power (dBm)
|
|
3679
|
+
set repeat on|off - Enable/disable repeating
|
|
3680
|
+
set radio f,bw,sf,cr - Set radio params (reboot to apply)
|
|
3681
|
+
set advert.interval <min> - Set advert interval (60-240 min)
|
|
3682
|
+
|
|
3683
|
+
{ANSI_BGREEN}System:{ANSI_END}
|
|
3684
|
+
reboot - Reboot device
|
|
3685
|
+
erase - Erase filesystem (serial only)
|
|
3686
|
+
|
|
3687
|
+
{ANSI_BYELLOW}Type 'quit' or 'q' to exit, Ctrl+C to abort{ANSI_END}
|
|
3688
|
+
"""
|
|
3689
|
+
|
|
3690
|
+
async def repeater_loop(port, baudrate):
|
|
3691
|
+
"""Interactive loop for repeater text CLI (raw serial commands)"""
|
|
3692
|
+
import serial as pyserial
|
|
3693
|
+
|
|
3694
|
+
print(f"{ANSI_BCYAN}Connecting to repeater at {port} ({baudrate} baud)...{ANSI_END}")
|
|
3695
|
+
try:
|
|
3696
|
+
ser = pyserial.Serial(port, baudrate, timeout=1)
|
|
3697
|
+
except PermissionError:
|
|
3698
|
+
print(f"{ANSI_BRED}Error: Permission denied. Try running with sudo or add user to dialout group.{ANSI_END}")
|
|
3699
|
+
return
|
|
3700
|
+
except Exception as e:
|
|
3701
|
+
print(f"{ANSI_BRED}Error opening serial port: {e}{ANSI_END}")
|
|
3702
|
+
return
|
|
3703
|
+
|
|
3704
|
+
await asyncio.sleep(0.5) # Wait for connection to stabilize
|
|
3705
|
+
ser.reset_input_buffer()
|
|
3706
|
+
|
|
3707
|
+
# Send initial CR to wake up CLI
|
|
3708
|
+
ser.write(b"\r")
|
|
3709
|
+
await asyncio.sleep(0.2)
|
|
3710
|
+
ser.reset_input_buffer()
|
|
3711
|
+
|
|
3712
|
+
# Try to get device info
|
|
3713
|
+
ser.write(b"ver\r")
|
|
3714
|
+
await asyncio.sleep(0.3)
|
|
3715
|
+
ver_response = ser.read(ser.in_waiting or 256).decode(errors='ignore').strip()
|
|
3716
|
+
device_name = "Repeater"
|
|
3717
|
+
for line in ver_response.split('\n'):
|
|
3718
|
+
line = line.strip()
|
|
3719
|
+
if line and not line.startswith("ver") and ">" not in line[:3]:
|
|
3720
|
+
device_name = line.split('(')[0].strip() if '(' in line else line
|
|
3721
|
+
break
|
|
3722
|
+
|
|
3723
|
+
print(f"{ANSI_BGREEN}Connected!{ANSI_END} Device: {ANSI_BMAGENTA}{device_name}{ANSI_END}")
|
|
3724
|
+
print(f"Type {ANSI_BCYAN}help{ANSI_END} for commands, {ANSI_BCYAN}quit{ANSI_END} to exit, {ANSI_BCYAN}Tab{ANSI_END} for completion")
|
|
3725
|
+
print("-" * 50)
|
|
3726
|
+
|
|
3727
|
+
# Setup history and session
|
|
3728
|
+
try:
|
|
3729
|
+
if os.path.isdir(MCCLI_CONFIG_DIR):
|
|
3730
|
+
our_history = FileHistory(MCCLI_REPEATER_HISTORY_FILE)
|
|
3731
|
+
else:
|
|
3732
|
+
our_history = None
|
|
3733
|
+
except Exception:
|
|
3734
|
+
our_history = None
|
|
3735
|
+
|
|
3736
|
+
session = PromptSession(
|
|
3737
|
+
history=our_history,
|
|
3738
|
+
wrap_lines=False,
|
|
3739
|
+
mouse_support=False,
|
|
3740
|
+
complete_style=CompleteStyle.MULTI_COLUMN
|
|
3741
|
+
)
|
|
3742
|
+
|
|
3743
|
+
# Setup key bindings
|
|
3744
|
+
bindings = KeyBindings()
|
|
3745
|
+
|
|
3746
|
+
@bindings.add("escape")
|
|
3747
|
+
def _(event):
|
|
3748
|
+
event.app.current_buffer.cancel_completion()
|
|
3749
|
+
|
|
3750
|
+
# Build prompt
|
|
3751
|
+
prompt_base = f"{ANSI_BGRAY}{device_name}{ANSI_MAGENTA}>{ANSI_END} "
|
|
3752
|
+
|
|
3753
|
+
# Setup completer
|
|
3754
|
+
completer = NestedCompleter.from_nested_dict(REPEATER_COMMANDS)
|
|
3755
|
+
|
|
3756
|
+
while True:
|
|
3757
|
+
try:
|
|
3758
|
+
cmd = await session.prompt_async(
|
|
3759
|
+
ANSI(prompt_base),
|
|
3760
|
+
completer=completer,
|
|
3761
|
+
complete_while_typing=False,
|
|
3762
|
+
key_bindings=bindings
|
|
3763
|
+
)
|
|
3764
|
+
except (KeyboardInterrupt, EOFError):
|
|
3765
|
+
break
|
|
3766
|
+
|
|
3767
|
+
cmd = cmd.strip()
|
|
3768
|
+
|
|
3769
|
+
if not cmd:
|
|
3770
|
+
continue
|
|
3771
|
+
|
|
3772
|
+
if cmd.lower() in ("quit", "exit", "q"):
|
|
3773
|
+
break
|
|
3774
|
+
|
|
3775
|
+
if cmd.lower() == "help":
|
|
3776
|
+
print(REPEATER_HELP)
|
|
3777
|
+
continue
|
|
3778
|
+
|
|
3779
|
+
# Send command with CR terminator
|
|
3780
|
+
ser.write(f"{cmd}\r".encode())
|
|
3781
|
+
await asyncio.sleep(0.3)
|
|
3782
|
+
|
|
3783
|
+
# Read response
|
|
3784
|
+
response = ser.read(ser.in_waiting or 4096).decode(errors='ignore')
|
|
3785
|
+
if response:
|
|
3786
|
+
# Clean up echo and format response
|
|
3787
|
+
lines = response.strip().split('\n')
|
|
3788
|
+
for line in lines:
|
|
3789
|
+
line = line.strip()
|
|
3790
|
+
if line and line != cmd: # Skip echo of command
|
|
3791
|
+
# Color code certain responses
|
|
3792
|
+
if line.startswith("OK") or line.startswith("ok"):
|
|
3793
|
+
print(f"{ANSI_GREEN}{line}{ANSI_END}")
|
|
3794
|
+
elif line.startswith("Error") or line.startswith("ERR"):
|
|
3795
|
+
print(f"{ANSI_RED}{line}{ANSI_END}")
|
|
3796
|
+
elif line.startswith("->"):
|
|
3797
|
+
print(f"{ANSI_CYAN}{line}{ANSI_END}")
|
|
3798
|
+
else:
|
|
3799
|
+
print(line)
|
|
3800
|
+
|
|
3801
|
+
ser.close()
|
|
3802
|
+
print(f"\n{ANSI_BGRAY}Disconnected from repeater.{ANSI_END}")
|
|
3803
|
+
|
|
3570
3804
|
async def main(argv):
|
|
3571
3805
|
""" Do the job """
|
|
3572
3806
|
json_output = JSON
|
|
@@ -3577,6 +3811,7 @@ async def main(argv):
|
|
|
3577
3811
|
hostname = None
|
|
3578
3812
|
serial_port = None
|
|
3579
3813
|
baudrate = 115200
|
|
3814
|
+
repeater_mode = False
|
|
3580
3815
|
timeout = 2
|
|
3581
3816
|
pin = None
|
|
3582
3817
|
first_device = False
|
|
@@ -3587,7 +3822,7 @@ async def main(argv):
|
|
|
3587
3822
|
address = f.readline().strip()
|
|
3588
3823
|
|
|
3589
3824
|
try:
|
|
3590
|
-
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:
|
|
3825
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:Cr")
|
|
3591
3826
|
except getopt.GetoptError:
|
|
3592
3827
|
print("Unrecognized option, use -h to get more help")
|
|
3593
3828
|
command_usage()
|
|
@@ -3599,6 +3834,8 @@ async def main(argv):
|
|
|
3599
3834
|
process_event_message.color = False
|
|
3600
3835
|
case "-C":
|
|
3601
3836
|
interactive_loop.classic = not interactive_loop.classic
|
|
3837
|
+
case "-r": # repeater mode (raw text CLI)
|
|
3838
|
+
repeater_mode = True
|
|
3602
3839
|
case "-d" : # name specified on cmdline
|
|
3603
3840
|
address = arg
|
|
3604
3841
|
case "-a" : # address specified on cmdline
|
|
@@ -3686,6 +3923,15 @@ async def main(argv):
|
|
|
3686
3923
|
elif (json_output) :
|
|
3687
3924
|
logger.setLevel(logging.ERROR)
|
|
3688
3925
|
|
|
3926
|
+
# Repeater mode - raw text CLI over serial
|
|
3927
|
+
if repeater_mode:
|
|
3928
|
+
if serial_port is None:
|
|
3929
|
+
print("Error: Repeater mode (-r) requires serial port (-s)")
|
|
3930
|
+
command_usage()
|
|
3931
|
+
return
|
|
3932
|
+
await repeater_loop(serial_port, baudrate)
|
|
3933
|
+
return
|
|
3934
|
+
|
|
3689
3935
|
mc = None
|
|
3690
3936
|
if not hostname is None : # connect via tcp
|
|
3691
3937
|
mc = await MeshCore.create_tcp(host=hostname, port=port, debug=debug, only_error=json_output)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.13
|
|
4
4
|
Summary: Command line interface to meshcore companion radios
|
|
5
5
|
Project-URL: Homepage, https://github.com/fdlamotte/meshcore-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/fdlamotte/meshcore-cli/issues
|
|
@@ -21,6 +21,18 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
|
|
22
22
|
meshcore-cli : CLI interface to MeschCore companion app over BLE, TCP or Serial
|
|
23
23
|
|
|
24
|
+
## About
|
|
25
|
+
|
|
26
|
+
meshcore-cli is a tool that connects to your companion radio node (meshcore client) over BLE, TCP or Serial and lets you interact with it from a terminal using a command line interface.
|
|
27
|
+
|
|
28
|
+
You can send commands as parameters to the meshcore-cli command (from your shell) either interactively or through a script.
|
|
29
|
+
|
|
30
|
+
There is also an interactive mode (this is the default when no command is passed). In interactive mode you can enter a contact (another client a repeater, a sensor or a room) and interact with it. For clients, interaction consists in sending/receiving messages. For repeaters, rooms or sensors it will directly give you the remote cli (you can still send messages to rooms using double quote prefix or msg command).
|
|
31
|
+
|
|
32
|
+
Note that meshcore-cli only interacts with companion radios (through BLE, Serial or TCP), you can't connect to a repeater using its serial interface.
|
|
33
|
+
|
|
34
|
+
Also, most meshcore companions only have one interface compiled in at a time. So you can't connect via Serial to a node, which has been compiled as a BLE companion.
|
|
35
|
+
|
|
24
36
|
## Install
|
|
25
37
|
|
|
26
38
|
Meshcore-cli depends on the [python meshcore](https://github.com/fdlamotte/meshcore_py) package. You can install both via `pip` or `pipx` using the command:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
|
|
3
|
+
meshcore_cli/meshcore_cli.py,sha256=J1etscwt3J88UzzeJZ9XvghQYwsZWL6k95P87PhAsSE,168812
|
|
4
|
+
meshcore_cli-1.3.13.dist-info/METADATA,sha256=J967l02FCI3aD51Hb0AjWVNfHUS__jdStVIINvgeRrQ,18194
|
|
5
|
+
meshcore_cli-1.3.13.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
meshcore_cli-1.3.13.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
+
meshcore_cli-1.3.13.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
+
meshcore_cli-1.3.13.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
|
|
3
|
-
meshcore_cli/meshcore_cli.py,sha256=ewDxGrG0U-5gT-SdTaKn-8tYzf0bzfrrqX-C47sk4Hs,160030
|
|
4
|
-
meshcore_cli-1.3.11.dist-info/METADATA,sha256=RaYfgG524SLlrIf2NBUwXP6npQgmzGp1edhUjo4aWPM,17138
|
|
5
|
-
meshcore_cli-1.3.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
-
meshcore_cli-1.3.11.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.3.11.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.3.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|