meshcore-cli 1.2.5__py3-none-any.whl → 1.2.6__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 +180 -19
- {meshcore_cli-1.2.5.dist-info → meshcore_cli-1.2.6.dist-info}/METADATA +2 -2
- meshcore_cli-1.2.6.dist-info/RECORD +8 -0
- meshcore_cli-1.2.5.dist-info/RECORD +0 -8
- {meshcore_cli-1.2.5.dist-info → meshcore_cli-1.2.6.dist-info}/WHEEL +0 -0
- {meshcore_cli-1.2.5.dist-info → meshcore_cli-1.2.6.dist-info}/entry_points.txt +0 -0
- {meshcore_cli-1.2.5.dist-info → meshcore_cli-1.2.6.dist-info}/licenses/LICENSE +0 -0
meshcore_cli/meshcore_cli.py
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
|
-
import os, sys
|
|
7
|
+
import os, sys, io
|
|
8
8
|
import time, datetime
|
|
9
9
|
import getopt, json, shlex, re
|
|
10
10
|
import logging
|
|
@@ -33,7 +33,7 @@ import re
|
|
|
33
33
|
from meshcore import MeshCore, EventType, logger
|
|
34
34
|
|
|
35
35
|
# Version
|
|
36
|
-
VERSION = "v1.2.
|
|
36
|
+
VERSION = "v1.2.6"
|
|
37
37
|
|
|
38
38
|
# default ble address is stored in a config file
|
|
39
39
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
|
@@ -208,15 +208,19 @@ async def handle_log_rx(event):
|
|
|
208
208
|
return
|
|
209
209
|
|
|
210
210
|
pkt = bytes().fromhex(event.payload["payload"])
|
|
211
|
+
pbuf = io.BytesIO(pkt)
|
|
212
|
+
header = pbuf.read(1)[0]
|
|
213
|
+
|
|
214
|
+
if header & ~1 == 0x14: # flood msg / channel
|
|
215
|
+
if handle_log_rx.channel_echoes:
|
|
216
|
+
if header & 1 == 0: # has transport code
|
|
217
|
+
pbuf.read(4) # discard transport code
|
|
218
|
+
path_len = pbuf.read(1)[0]
|
|
219
|
+
path = pbuf.read(path_len).hex()
|
|
220
|
+
chan_hash = pbuf.read(1).hex()
|
|
221
|
+
cipher_mac = pbuf.read(2)
|
|
222
|
+
msg = pbuf.read() # until the end of buffer
|
|
211
223
|
|
|
212
|
-
if handle_log_rx.channel_echoes:
|
|
213
|
-
if pkt[0] == 0x15:
|
|
214
|
-
chan_name = ""
|
|
215
|
-
path_len = pkt[1]
|
|
216
|
-
path = pkt[2:path_len+2].hex()
|
|
217
|
-
chan_hash = pkt[path_len+2:path_len+3].hex()
|
|
218
|
-
cipher_mac = pkt[path_len+3:path_len+5]
|
|
219
|
-
msg = pkt[path_len+5:]
|
|
220
224
|
channel = None
|
|
221
225
|
for c in await get_channels(mc):
|
|
222
226
|
if c["channel_hash"] == chan_hash : # validate against MAC
|
|
@@ -226,6 +230,8 @@ async def handle_log_rx(event):
|
|
|
226
230
|
channel = c
|
|
227
231
|
break
|
|
228
232
|
|
|
233
|
+
chan_name = ""
|
|
234
|
+
|
|
229
235
|
if channel is None :
|
|
230
236
|
if handle_log_rx.echo_unk_chans:
|
|
231
237
|
chan_name = chan_hash
|
|
@@ -235,7 +241,7 @@ async def handle_log_rx(event):
|
|
|
235
241
|
aes_key = bytes.fromhex(channel["channel_secret"])
|
|
236
242
|
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
237
243
|
message = cipher.decrypt(msg)[5:].decode("utf-8").strip("\x00")
|
|
238
|
-
|
|
244
|
+
|
|
239
245
|
if chan_name != "" :
|
|
240
246
|
width = os.get_terminal_size().columns
|
|
241
247
|
cars = width - 13 - 2 * path_len - len(chan_name) - 1
|
|
@@ -245,7 +251,7 @@ async def handle_log_rx(event):
|
|
|
245
251
|
print_above(txt)
|
|
246
252
|
else:
|
|
247
253
|
print(txt)
|
|
248
|
-
|
|
254
|
+
|
|
249
255
|
handle_log_rx.json_log_rx = False
|
|
250
256
|
handle_log_rx.channel_echoes = False
|
|
251
257
|
handle_log_rx.mc = None
|
|
@@ -322,7 +328,7 @@ async def log_message(mc, msg):
|
|
|
322
328
|
if msg["type"] == "PRIV" :
|
|
323
329
|
ct = mc.get_contact_by_key_prefix(msg['pubkey_prefix'])
|
|
324
330
|
if ct is None:
|
|
325
|
-
msg["name"] =
|
|
331
|
+
msg["name"] = msg["pubkey_prefix"]
|
|
326
332
|
else:
|
|
327
333
|
msg["name"] = ct["adv_name"]
|
|
328
334
|
elif msg["type"] == "CHAN" :
|
|
@@ -366,7 +372,7 @@ async def subscribe_to_msgs(mc, json_output=False, above=False):
|
|
|
366
372
|
class MyNestedCompleter(NestedCompleter):
|
|
367
373
|
def get_completions( self, document, complete_event):
|
|
368
374
|
txt = document.text_before_cursor.lstrip()
|
|
369
|
-
if not " " in txt:
|
|
375
|
+
if not " " in txt:
|
|
370
376
|
if txt != "" and txt[0] == "/" and txt.count("/") == 1:
|
|
371
377
|
opts = []
|
|
372
378
|
for k in self.options.keys():
|
|
@@ -465,6 +471,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
465
471
|
"set_channel": None,
|
|
466
472
|
"get_channels": None,
|
|
467
473
|
"remove_channel": None,
|
|
474
|
+
"apply_to": None,
|
|
475
|
+
"at": None,
|
|
468
476
|
"set" : {
|
|
469
477
|
"name" : None,
|
|
470
478
|
"pin" : None,
|
|
@@ -531,6 +539,8 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
531
539
|
|
|
532
540
|
contact_completion_list = {
|
|
533
541
|
"contact_info": None,
|
|
542
|
+
"contact_name": None,
|
|
543
|
+
"contact_lastmod": None,
|
|
534
544
|
"export_contact" : None,
|
|
535
545
|
"share_contact" : None,
|
|
536
546
|
"upload_contact" : None,
|
|
@@ -652,7 +662,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
|
|
|
652
662
|
slash_root_completion_list = {}
|
|
653
663
|
for k,v in root_completion_list.items():
|
|
654
664
|
slash_root_completion_list["/"+k]=v
|
|
655
|
-
|
|
665
|
+
|
|
656
666
|
completion_list.update(slash_root_completion_list)
|
|
657
667
|
|
|
658
668
|
slash_contacts_completion_list = {}
|
|
@@ -802,6 +812,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
802
812
|
if line == "" : # blank line
|
|
803
813
|
pass
|
|
804
814
|
|
|
815
|
+
elif line.startswith("?") :
|
|
816
|
+
get_help_for(line[1:], context="chat")
|
|
817
|
+
|
|
805
818
|
# raw meshcli command as on command line
|
|
806
819
|
elif line.startswith("$") :
|
|
807
820
|
try :
|
|
@@ -905,6 +918,13 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
|
|
|
905
918
|
if last_ack == False :
|
|
906
919
|
contact = ln
|
|
907
920
|
|
|
921
|
+
elif contact is None and\
|
|
922
|
+
(line.startswith("apply_to ") or line.startswith("at ")):
|
|
923
|
+
try:
|
|
924
|
+
await apply_command_to_contacts(mc, line.split(" ",2)[1], line.split(" ",2)[2])
|
|
925
|
+
except IndexError:
|
|
926
|
+
logger.error(f"Error with apply_to command parameters")
|
|
927
|
+
|
|
908
928
|
# commands are passed through if at root
|
|
909
929
|
elif contact is None or line.startswith(".") :
|
|
910
930
|
try:
|
|
@@ -966,6 +986,23 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
966
986
|
await process_cmds(mc, args)
|
|
967
987
|
return True
|
|
968
988
|
|
|
989
|
+
if line.startswith("contact_name") or line.startswith("cn"):
|
|
990
|
+
print(contact['adv_name'],end="")
|
|
991
|
+
if " " in line:
|
|
992
|
+
print(" ", end="", flush=True)
|
|
993
|
+
secline = line.split(" ", 1)[1]
|
|
994
|
+
await process_contact_chat_line(mc, contact, secline)
|
|
995
|
+
else:
|
|
996
|
+
print("")
|
|
997
|
+
return True
|
|
998
|
+
|
|
999
|
+
if line == "contact_lastmod":
|
|
1000
|
+
timestamp = contact["lastmod"]
|
|
1001
|
+
print(f"{contact['adv_name']} updated"
|
|
1002
|
+
f" {datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d at %H:%M:%S')}"
|
|
1003
|
+
f" ({timestamp})")
|
|
1004
|
+
return True
|
|
1005
|
+
|
|
969
1006
|
# commands that take contact as second arg will be sent to recipient
|
|
970
1007
|
if line == "sc" or line == "share_contact" or\
|
|
971
1008
|
line == "ec" or line == "export_contact" or\
|
|
@@ -1053,7 +1090,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1053
1090
|
return True
|
|
1054
1091
|
|
|
1055
1092
|
# same but for commands with a parameter
|
|
1056
|
-
if line.startswith("cmd ") or\
|
|
1093
|
+
if line.startswith("cmd ") or line.startswith("msg ") or\
|
|
1057
1094
|
line.startswith("cp ") or line.startswith("change_path ") or\
|
|
1058
1095
|
line.startswith("cf ") or line.startswith("change_flags ") or\
|
|
1059
1096
|
line.startswith("req_binary ") or\
|
|
@@ -1085,7 +1122,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1085
1122
|
|
|
1086
1123
|
if password == "":
|
|
1087
1124
|
try:
|
|
1088
|
-
sess = PromptSession("Password: ", is_password=True)
|
|
1125
|
+
sess = PromptSession(f"Password for {contact['adv_name']}: ", is_password=True)
|
|
1089
1126
|
password = await sess.prompt_async()
|
|
1090
1127
|
except EOFError:
|
|
1091
1128
|
logger.info("Canceled")
|
|
@@ -1124,6 +1161,89 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1124
1161
|
|
|
1125
1162
|
return False
|
|
1126
1163
|
|
|
1164
|
+
async def apply_command_to_contacts(mc, contact_filter, line):
|
|
1165
|
+
upd_before = None
|
|
1166
|
+
upd_after = None
|
|
1167
|
+
contact_type = None
|
|
1168
|
+
min_hops = None
|
|
1169
|
+
max_hops = None
|
|
1170
|
+
|
|
1171
|
+
await mc.ensure_contacts()
|
|
1172
|
+
|
|
1173
|
+
filters = contact_filter.split(",")
|
|
1174
|
+
for f in filters:
|
|
1175
|
+
if f == "all":
|
|
1176
|
+
pass
|
|
1177
|
+
elif f[0] == "u": #updated
|
|
1178
|
+
val_str = f[2:]
|
|
1179
|
+
t = time.time()
|
|
1180
|
+
if val_str[-1] == "d": # value in days
|
|
1181
|
+
t = t - float(val_str[0:-1]) * 86400
|
|
1182
|
+
elif val_str[-1] == "h": # value in hours
|
|
1183
|
+
t = t - float(val_str[0:-1]) * 3600
|
|
1184
|
+
elif val_str[-1] == "m": # value in minutes
|
|
1185
|
+
t = t - float(val_str[0:-1]) * 60
|
|
1186
|
+
else:
|
|
1187
|
+
t = int(val_str)
|
|
1188
|
+
if f[1] == "<": #before
|
|
1189
|
+
upd_before = t
|
|
1190
|
+
elif f[1] == ">":
|
|
1191
|
+
upd_after = t
|
|
1192
|
+
else:
|
|
1193
|
+
logger.error(f"Time filter can only be < or >")
|
|
1194
|
+
return
|
|
1195
|
+
elif f[0] == "t": # type
|
|
1196
|
+
if f[1] == "=":
|
|
1197
|
+
contact_type = int(f[2:])
|
|
1198
|
+
else:
|
|
1199
|
+
logger.error(f"Type can only be equals to a value")
|
|
1200
|
+
return
|
|
1201
|
+
elif f[0] == "d": # direct
|
|
1202
|
+
min_hops=0
|
|
1203
|
+
elif f[0] == "f": # flood
|
|
1204
|
+
max_hops=-1
|
|
1205
|
+
elif f[0] == "h": # hop number
|
|
1206
|
+
if f[1] == ">":
|
|
1207
|
+
min_hops = int(f[2:])+1
|
|
1208
|
+
elif f[1] == "<":
|
|
1209
|
+
max_hops = int(f[2:])-1
|
|
1210
|
+
elif f[1] == "=":
|
|
1211
|
+
min_hops = int(f[2:])
|
|
1212
|
+
max_hops = int(f[2:])
|
|
1213
|
+
else:
|
|
1214
|
+
logger.error(f"Unknown filter {f}")
|
|
1215
|
+
return
|
|
1216
|
+
|
|
1217
|
+
for c in dict(mc._contacts).items():
|
|
1218
|
+
contact = c[1]
|
|
1219
|
+
if (contact_type is None or contact["type"] == contact_type) and\
|
|
1220
|
+
(upd_before is None or contact["lastmod"] < upd_before) and\
|
|
1221
|
+
(upd_after is None or contact["lastmod"] > upd_after) and\
|
|
1222
|
+
(min_hops is None or contact["out_path_len"] >= min_hops) and\
|
|
1223
|
+
(max_hops is None or contact["out_path_len"] <= max_hops):
|
|
1224
|
+
if await process_contact_chat_line(mc, contact, line):
|
|
1225
|
+
pass
|
|
1226
|
+
|
|
1227
|
+
elif line == "remove_contact":
|
|
1228
|
+
args = [line, contact['adv_name']]
|
|
1229
|
+
await process_cmds(mc, args)
|
|
1230
|
+
|
|
1231
|
+
elif line.startswith("send") or line.startswith("\"") :
|
|
1232
|
+
if line.startswith("send") :
|
|
1233
|
+
line = line[5:]
|
|
1234
|
+
if line.startswith("\"") :
|
|
1235
|
+
line = line[1:]
|
|
1236
|
+
await msg_ack(mc, contact, line)
|
|
1237
|
+
|
|
1238
|
+
elif contact["type"] == 2 or\
|
|
1239
|
+
contact["type"] == 3 or\
|
|
1240
|
+
contact["type"] == 4 : # repeater, room, sensor send cmd
|
|
1241
|
+
await process_cmds(mc, ["cmd", contact["adv_name"], line])
|
|
1242
|
+
# wait for a reply from cmd
|
|
1243
|
+
await mc.wait_for_event(EventType.MESSAGES_WAITING, timeout=7)
|
|
1244
|
+
|
|
1245
|
+
else:
|
|
1246
|
+
logger.error(f"Can't send {line} to {contact['adv_name']}")
|
|
1127
1247
|
|
|
1128
1248
|
async def send_cmd (mc, contact, cmd) :
|
|
1129
1249
|
res = await mc.commands.send_cmd(contact, cmd)
|
|
@@ -1395,6 +1515,11 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1395
1515
|
""" process next command """
|
|
1396
1516
|
try :
|
|
1397
1517
|
argnum = 0
|
|
1518
|
+
|
|
1519
|
+
if cmds[0].startswith("?") : # get some help
|
|
1520
|
+
get_help_for(cmds[0][1:], context="line")
|
|
1521
|
+
return cmds[argnum+1:]
|
|
1522
|
+
|
|
1398
1523
|
if cmds[0].startswith(".") : # override json_output
|
|
1399
1524
|
json_output = True
|
|
1400
1525
|
cmd = cmds[0][1:]
|
|
@@ -1485,6 +1610,10 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1485
1610
|
else:
|
|
1486
1611
|
print("Time set")
|
|
1487
1612
|
|
|
1613
|
+
case "apply_to"|"at":
|
|
1614
|
+
argnum = 2
|
|
1615
|
+
await apply_command_to_contacts(mc, cmds[1], cmds[2])
|
|
1616
|
+
|
|
1488
1617
|
case "set":
|
|
1489
1618
|
argnum = 2
|
|
1490
1619
|
match cmds[1]:
|
|
@@ -1979,6 +2108,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
1979
2108
|
if res is None:
|
|
1980
2109
|
print("Error setting channel")
|
|
1981
2110
|
|
|
2111
|
+
case "scope":
|
|
2112
|
+
argnum = 1
|
|
2113
|
+
res = await mc.commands.set_flood_scope(cmds[1])
|
|
2114
|
+
if res is None or res.type == EventType.ERROR:
|
|
2115
|
+
print(f"Error while setting scope")
|
|
2116
|
+
|
|
1982
2117
|
case "remove_channel":
|
|
1983
2118
|
argnum = 1
|
|
1984
2119
|
res = await set_channel(mc, cmds[1], "", bytes.fromhex(16*"00"))
|
|
@@ -2718,7 +2853,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2718
2853
|
await mc.ensure_contacts()
|
|
2719
2854
|
contact = mc.get_contact_by_name(cmds[0])
|
|
2720
2855
|
if contact is None:
|
|
2721
|
-
logger.error(f"Unknown command : {cmd}
|
|
2856
|
+
logger.error(f"Unknown command : {cmd}, {cmds} not executed ...")
|
|
2722
2857
|
return None
|
|
2723
2858
|
|
|
2724
2859
|
await interactive_loop(mc, to=contact)
|
|
@@ -2762,7 +2897,8 @@ def version():
|
|
|
2762
2897
|
print (f"meshcore-cli: command line interface to MeshCore companion radios {VERSION}")
|
|
2763
2898
|
|
|
2764
2899
|
def command_help():
|
|
2765
|
-
print("""
|
|
2900
|
+
print(""" ?<cmd> may give you some more help about cmd
|
|
2901
|
+
General commands
|
|
2766
2902
|
chat : enter the chat (interactive) mode
|
|
2767
2903
|
chat_to <ct> : enter chat with contact to
|
|
2768
2904
|
script <filename> : execute commands in filename
|
|
@@ -2773,6 +2909,7 @@ def command_help():
|
|
|
2773
2909
|
reboot : reboots node
|
|
2774
2910
|
sleep <secs> : sleeps for a given amount of secs s
|
|
2775
2911
|
wait_key : wait until user presses <Enter> wk
|
|
2912
|
+
apply_to <scope> <cmds>: sends cmds to contacts matching scope at
|
|
2776
2913
|
Messenging
|
|
2777
2914
|
msg <name> <msg> : send message to node by name m {
|
|
2778
2915
|
wait_ack : wait an ack wa }
|
|
@@ -2847,6 +2984,30 @@ def usage () :
|
|
|
2847
2984
|
Available Commands and shorcuts (can be chained) :""")
|
|
2848
2985
|
command_help()
|
|
2849
2986
|
|
|
2987
|
+
def get_help_for (cmdname, context="line") :
|
|
2988
|
+
if cmdname == "apply_to" or cmdname == "at" :
|
|
2989
|
+
print("""apply_to <scope> <cmd> : applies cmd to contacts matching scope
|
|
2990
|
+
Scope acts like a filter with comma separated fields :
|
|
2991
|
+
- u, matches modification time < or > than a timestamp
|
|
2992
|
+
(can also be days hours or minutes ago if followed by d,h or m)
|
|
2993
|
+
- t, matches the type (1: client, 2: repeater, 3: room, 4: sensor)
|
|
2994
|
+
- h, matches number of hops
|
|
2995
|
+
- d, direct, similar to h>-1
|
|
2996
|
+
- f, flood, similar to h<0 or h=-1
|
|
2997
|
+
|
|
2998
|
+
Note: Some commands like contact_name (aka cn), reset_path (aka rp), forget_password (aka fp) can be chained.
|
|
2999
|
+
|
|
3000
|
+
Examples:
|
|
3001
|
+
# removes all clients that have not been updated in last 2 days
|
|
3002
|
+
at u<2d,t=1 remove_contact
|
|
3003
|
+
# gives traces to repeaters that have been updated in the last 24h and are direct
|
|
3004
|
+
at t=2,u>1d,d cn trace
|
|
3005
|
+
# tries to do flood login to all repeaters
|
|
3006
|
+
at t=2 rp login
|
|
3007
|
+
""")
|
|
3008
|
+
else:
|
|
3009
|
+
print(f"Sorry, no help yet for {cmdname}")
|
|
3010
|
+
|
|
2850
3011
|
async def main(argv):
|
|
2851
3012
|
""" Do the job """
|
|
2852
3013
|
json_output = JSON
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.6
|
|
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
|
|
@@ -10,7 +10,7 @@ License-File: LICENSE
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
|
-
Requires-Dist: meshcore>=2.1.
|
|
13
|
+
Requires-Dist: meshcore>=2.1.20
|
|
14
14
|
Requires-Dist: prompt-toolkit>=3.0.50
|
|
15
15
|
Requires-Dist: pycryptodome
|
|
16
16
|
Requires-Dist: requests>=2.28.0
|
|
@@ -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=HrfCnKDBvC-Lni4X4rWlSuBnadmRuVK2f5Z7HK0jHNo,134323
|
|
4
|
+
meshcore_cli-1.2.6.dist-info/METADATA,sha256=rsIFIQm9Ne8Ft-ax8PCGAnfpjymhCNjxVg5z4CRDF7s,11657
|
|
5
|
+
meshcore_cli-1.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
+
meshcore_cli-1.2.6.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
+
meshcore_cli-1.2.6.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
+
meshcore_cli-1.2.6.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=Vh-oA4_FLEzlvS_6bVan8vyuXwOo3bIbajG-1_eZXck,128150
|
|
4
|
-
meshcore_cli-1.2.5.dist-info/METADATA,sha256=_7w4NJP4SlJjWfZ9qNRjfi25k5EmwtzHcH5OAzZ9MbE,11657
|
|
5
|
-
meshcore_cli-1.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
6
|
-
meshcore_cli-1.2.5.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
|
|
7
|
-
meshcore_cli-1.2.5.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
|
|
8
|
-
meshcore_cli-1.2.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|