neuronum 5.9.0__py3-none-any.whl → 6.0.0__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.
Potentially problematic release.
This version of neuronum might be problematic. Click here for more details.
- cli/main.py +1114 -1143
- neuronum/__init__.py +1 -1
- neuronum/neuronum.py +519 -519
- {neuronum-5.9.0.dist-info → neuronum-6.0.0.dist-info}/METADATA +252 -248
- neuronum-6.0.0.dist-info/RECORD +10 -0
- {neuronum-5.9.0.dist-info → neuronum-6.0.0.dist-info}/licenses/LICENSE.md +47 -47
- neuronum-5.9.0.dist-info/RECORD +0 -10
- {neuronum-5.9.0.dist-info → neuronum-6.0.0.dist-info}/WHEEL +0 -0
- {neuronum-5.9.0.dist-info → neuronum-6.0.0.dist-info}/entry_points.txt +0 -0
- {neuronum-5.9.0.dist-info → neuronum-6.0.0.dist-info}/top_level.txt +0 -0
cli/main.py
CHANGED
|
@@ -1,1143 +1,1114 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import os
|
|
3
|
-
import neuronum
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@click.group()
|
|
18
|
-
def cli():
|
|
19
|
-
"""Neuronum CLI Tool"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@click.command()
|
|
23
|
-
def create_cell():
|
|
24
|
-
cell_type = questionary.select(
|
|
25
|
-
"Choose Cell type:",
|
|
26
|
-
choices=["business", "community"]
|
|
27
|
-
).ask()
|
|
28
|
-
|
|
29
|
-
network = questionary.select(
|
|
30
|
-
"Choose Network:",
|
|
31
|
-
choices=["neuronum.net"]
|
|
32
|
-
).ask()
|
|
33
|
-
|
|
34
|
-
if cell_type == "business":
|
|
35
|
-
click.echo("Visit https://neuronum.net/createcell to create your Neuronum Business Cell")
|
|
36
|
-
|
|
37
|
-
if cell_type == "community":
|
|
38
|
-
|
|
39
|
-
email = click.prompt("Enter email")
|
|
40
|
-
password = click.prompt("Enter password", hide_input=True)
|
|
41
|
-
repeat_password = click.prompt("Repeat password", hide_input=True)
|
|
42
|
-
|
|
43
|
-
if password != repeat_password:
|
|
44
|
-
click.echo("Passwords do not match!")
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
url = f"https://{network}/api/create_cell/{cell_type}"
|
|
48
|
-
|
|
49
|
-
create_cell = {"email": email, "password": password}
|
|
50
|
-
|
|
51
|
-
try:
|
|
52
|
-
response = requests.post(url, json=create_cell)
|
|
53
|
-
response.raise_for_status()
|
|
54
|
-
status = response.json()["status"]
|
|
55
|
-
|
|
56
|
-
except requests.exceptions.RequestException as e:
|
|
57
|
-
click.echo(f"Error sending request: {e}")
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
if status == True:
|
|
61
|
-
host = response.json()["host"]
|
|
62
|
-
cellkey = click.prompt(f"Please verify your email address with the Cell Key send to {email}")
|
|
63
|
-
|
|
64
|
-
url = f"https://{network}/api/verify_email"
|
|
65
|
-
|
|
66
|
-
verify_email = {"host": host, "email": email, "cellkey": cellkey}
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
response = requests.post(url, json=verify_email)
|
|
70
|
-
response.raise_for_status()
|
|
71
|
-
status = response.json()["status"]
|
|
72
|
-
|
|
73
|
-
except requests.exceptions.RequestException as e:
|
|
74
|
-
click.echo(f"Error sending request: {e}")
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
if status == True:
|
|
78
|
-
synapse = response.json()["synapse"]
|
|
79
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
80
|
-
credentials_folder_path.mkdir(parents=True, exist_ok=True)
|
|
81
|
-
|
|
82
|
-
env_path = credentials_folder_path / ".env"
|
|
83
|
-
env_path.write_text(f"HOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
|
|
84
|
-
|
|
85
|
-
click.echo(f"Welcome to Neuronum! Community Cell '{host}' created and connected!")
|
|
86
|
-
|
|
87
|
-
if status == False:
|
|
88
|
-
click.echo(f"Error:'{email}' already assigned!")
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@click.command()
|
|
92
|
-
def connect_cell():
|
|
93
|
-
email = click.prompt("Enter your Email")
|
|
94
|
-
password = click.prompt("Enter password", hide_input=True)
|
|
95
|
-
|
|
96
|
-
network = questionary.select(
|
|
97
|
-
"Choose Network:",
|
|
98
|
-
choices=["neuronum.net"]
|
|
99
|
-
).ask()
|
|
100
|
-
|
|
101
|
-
url = f"https://{network}/api/connect_cell"
|
|
102
|
-
payload = {"email": email, "password": password}
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
response = requests.post(url, json=payload)
|
|
106
|
-
response.raise_for_status()
|
|
107
|
-
status = response.json()["status"]
|
|
108
|
-
host = response.json()["host"]
|
|
109
|
-
except requests.exceptions.RequestException as e:
|
|
110
|
-
click.echo(f"Error connecting: {e}")
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
if status == True:
|
|
114
|
-
cellkey = click.prompt(f"Please verify your email address with the Cell Key send to {email}")
|
|
115
|
-
url = f"https://{network}/api/verify_email"
|
|
116
|
-
verify_email = {"host": host, "email": email, "cellkey": cellkey}
|
|
117
|
-
|
|
118
|
-
try:
|
|
119
|
-
response = requests.post(url, json=verify_email)
|
|
120
|
-
response.raise_for_status()
|
|
121
|
-
status = response.json()["status"]
|
|
122
|
-
synapse = response.json()["synapse"]
|
|
123
|
-
|
|
124
|
-
except requests.exceptions.RequestException as e:
|
|
125
|
-
click.echo(f"Error sending request: {e}")
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
if status == True:
|
|
129
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
130
|
-
credentials_folder_path.mkdir(parents=True, exist_ok=True)
|
|
131
|
-
|
|
132
|
-
env_path = credentials_folder_path / f".env"
|
|
133
|
-
env_path.write_text(f"HOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
|
|
134
|
-
|
|
135
|
-
click.echo(f"Cell '{host}' connected!")
|
|
136
|
-
else:
|
|
137
|
-
click.echo(f"Connection failed!")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@click.command()
|
|
141
|
-
def view_cell():
|
|
142
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
143
|
-
env_path = credentials_folder_path / ".env"
|
|
144
|
-
|
|
145
|
-
env_data = {}
|
|
146
|
-
|
|
147
|
-
try:
|
|
148
|
-
with open(env_path, "r") as f:
|
|
149
|
-
for line in f:
|
|
150
|
-
key, value = line.strip().split("=")
|
|
151
|
-
env_data[key] = value
|
|
152
|
-
|
|
153
|
-
host = env_data.get("HOST", "")
|
|
154
|
-
|
|
155
|
-
except FileNotFoundError:
|
|
156
|
-
click.echo("Error: No credentials found. Please connect to a cell first.")
|
|
157
|
-
return
|
|
158
|
-
except Exception as e:
|
|
159
|
-
click.echo(f"Error reading .env file: {e}")
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
if host:
|
|
163
|
-
click.echo(f"Connected Cell: '{host}'")
|
|
164
|
-
else:
|
|
165
|
-
click.echo("No active cell connection found.")
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
@click.command()
|
|
169
|
-
def disconnect_cell():
|
|
170
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
171
|
-
env_path = credentials_folder_path / ".env"
|
|
172
|
-
|
|
173
|
-
env_data = {}
|
|
174
|
-
|
|
175
|
-
try:
|
|
176
|
-
with open(env_path, "r") as f:
|
|
177
|
-
for line in f:
|
|
178
|
-
key, value = line.strip().split("=")
|
|
179
|
-
env_data[key] = value
|
|
180
|
-
|
|
181
|
-
host = env_data.get("HOST", "")
|
|
182
|
-
|
|
183
|
-
except FileNotFoundError:
|
|
184
|
-
click.echo("Error: .env with credentials not found")
|
|
185
|
-
return
|
|
186
|
-
except Exception as e:
|
|
187
|
-
click.echo(f"Error reading .env file: {e}")
|
|
188
|
-
return
|
|
189
|
-
|
|
190
|
-
if env_path.exists():
|
|
191
|
-
if click.confirm(f"Are you sure you want to disconnect Cell '{host}'?", default=True):
|
|
192
|
-
os.remove(env_path)
|
|
193
|
-
click.echo(f"'{host}' disconnected!")
|
|
194
|
-
else:
|
|
195
|
-
click.echo("Disconnect canceled.")
|
|
196
|
-
else:
|
|
197
|
-
click.echo(f"No Neuronum Cell connected!")
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
@click.command()
|
|
201
|
-
def delete_cell():
|
|
202
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
203
|
-
env_path = credentials_folder_path / ".env"
|
|
204
|
-
|
|
205
|
-
env_data = {}
|
|
206
|
-
|
|
207
|
-
try:
|
|
208
|
-
with open(env_path, "r") as f:
|
|
209
|
-
for line in f:
|
|
210
|
-
key, value = line.strip().split("=")
|
|
211
|
-
env_data[key] = value
|
|
212
|
-
|
|
213
|
-
host = env_data.get("HOST", "")
|
|
214
|
-
password = env_data.get("PASSWORD", "")
|
|
215
|
-
network = env_data.get("NETWORK", "")
|
|
216
|
-
synapse = env_data.get("SYNAPSE", "")
|
|
217
|
-
|
|
218
|
-
except FileNotFoundError:
|
|
219
|
-
click.echo("Error: No cell connected. Connect Cell first to delete")
|
|
220
|
-
return
|
|
221
|
-
except Exception as e:
|
|
222
|
-
click.echo(f"Error reading .env file: {e}")
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
confirm = click.confirm(f" Are you sure you want to delete '{host}'?", default=True)
|
|
226
|
-
if not confirm:
|
|
227
|
-
click.echo("Deletion canceled.")
|
|
228
|
-
return
|
|
229
|
-
|
|
230
|
-
url = f"https://{network}/api/delete_cell"
|
|
231
|
-
payload = {"host": host, "password": password, "synapse": synapse}
|
|
232
|
-
|
|
233
|
-
try:
|
|
234
|
-
response = requests.delete(url, json=payload)
|
|
235
|
-
response.raise_for_status()
|
|
236
|
-
status = response.json()["status"]
|
|
237
|
-
except requests.exceptions.RequestException as e:
|
|
238
|
-
click.echo(f"Error deleting cell: {e}")
|
|
239
|
-
return
|
|
240
|
-
|
|
241
|
-
if status == True:
|
|
242
|
-
env_path = credentials_folder_path / f"{host}.env"
|
|
243
|
-
if env_path.exists():
|
|
244
|
-
os.remove(env_path)
|
|
245
|
-
click.echo("Credentials deleted successfully!")
|
|
246
|
-
click.echo(f"Neuronum Cell '{host}' has been deleted!")
|
|
247
|
-
else:
|
|
248
|
-
click.echo(f"Neuronum Cell '{host}' deletion failed!")
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
@click.command()
|
|
252
|
-
@click.option('--sync', multiple=True, default=None, help="Optional stream IDs for sync.")
|
|
253
|
-
@click.option('--stream', multiple=True, default=None, help="Optional stream ID for stream.")
|
|
254
|
-
@click.option('--app', is_flag=True, help="Generate a Node with app template")
|
|
255
|
-
def init_node(sync, stream, app):
|
|
256
|
-
descr = click.prompt("Node description: Type up to 25 characters").strip()
|
|
257
|
-
if descr and len(descr) > 25:
|
|
258
|
-
click.echo("Description too long. Max 25 characters allowed.")
|
|
259
|
-
return
|
|
260
|
-
asyncio.run(async_init_node(sync, stream, app, descr))
|
|
261
|
-
|
|
262
|
-
async def async_init_node(sync, stream, app, descr):
|
|
263
|
-
credentials_folder_path = Path.home() / ".neuronum"
|
|
264
|
-
env_path = credentials_folder_path / ".env"
|
|
265
|
-
|
|
266
|
-
env_data = {}
|
|
267
|
-
|
|
268
|
-
try:
|
|
269
|
-
with open(env_path, "r") as f:
|
|
270
|
-
for line in f:
|
|
271
|
-
key, value = line.strip().split("=")
|
|
272
|
-
env_data[key] = value
|
|
273
|
-
|
|
274
|
-
host = env_data.get("HOST", "")
|
|
275
|
-
password = env_data.get("PASSWORD", "")
|
|
276
|
-
network = env_data.get("NETWORK", "")
|
|
277
|
-
synapse = env_data.get("SYNAPSE", "")
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
"
|
|
350
|
-
"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
)
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
""
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
""
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
""
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
cell
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
""
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
except FileNotFoundError:
|
|
887
|
-
click.echo("Error: .
|
|
888
|
-
return
|
|
889
|
-
except Exception as e:
|
|
890
|
-
click.echo(f"Error reading .
|
|
891
|
-
return
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
async for operation in cell.sync() if stx is None else cell.sync(stx):
|
|
1116
|
-
label = operation.get("label")
|
|
1117
|
-
data = operation.get("data")
|
|
1118
|
-
ts = operation.get("time")
|
|
1119
|
-
stxID = operation.get("stxID")
|
|
1120
|
-
operator = operation.get("operator")
|
|
1121
|
-
txID = operation.get("txID")
|
|
1122
|
-
print(label, data, ts, operator, txID, stxID)
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
cli.add_command(create_cell)
|
|
1126
|
-
cli.add_command(connect_cell)
|
|
1127
|
-
cli.add_command(view_cell)
|
|
1128
|
-
cli.add_command(disconnect_cell)
|
|
1129
|
-
cli.add_command(delete_cell)
|
|
1130
|
-
cli.add_command(init_node)
|
|
1131
|
-
cli.add_command(start_node)
|
|
1132
|
-
cli.add_command(restart_node)
|
|
1133
|
-
cli.add_command(stop_node)
|
|
1134
|
-
cli.add_command(check_node)
|
|
1135
|
-
cli.add_command(update_node)
|
|
1136
|
-
cli.add_command(delete_node)
|
|
1137
|
-
cli.add_command(activate)
|
|
1138
|
-
cli.add_command(load)
|
|
1139
|
-
cli.add_command(sync)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
if __name__ == "__main__":
|
|
1143
|
-
cli()
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
import neuronum
|
|
4
|
+
import platform
|
|
5
|
+
import glob
|
|
6
|
+
import asyncio
|
|
7
|
+
import aiohttp
|
|
8
|
+
import click
|
|
9
|
+
import questionary
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import requests
|
|
12
|
+
import psutil
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.group()
|
|
18
|
+
def cli():
|
|
19
|
+
"""Neuronum CLI Tool"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
def create_cell():
|
|
24
|
+
cell_type = questionary.select(
|
|
25
|
+
"Choose Cell type:",
|
|
26
|
+
choices=["business", "community"]
|
|
27
|
+
).ask()
|
|
28
|
+
|
|
29
|
+
network = questionary.select(
|
|
30
|
+
"Choose Network:",
|
|
31
|
+
choices=["neuronum.net"]
|
|
32
|
+
).ask()
|
|
33
|
+
|
|
34
|
+
if cell_type == "business":
|
|
35
|
+
click.echo("Visit https://neuronum.net/createcell to create your Neuronum Business Cell")
|
|
36
|
+
|
|
37
|
+
if cell_type == "community":
|
|
38
|
+
|
|
39
|
+
email = click.prompt("Enter email")
|
|
40
|
+
password = click.prompt("Enter password", hide_input=True)
|
|
41
|
+
repeat_password = click.prompt("Repeat password", hide_input=True)
|
|
42
|
+
|
|
43
|
+
if password != repeat_password:
|
|
44
|
+
click.echo("Passwords do not match!")
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
url = f"https://{network}/api/create_cell/{cell_type}"
|
|
48
|
+
|
|
49
|
+
create_cell = {"email": email, "password": password}
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
response = requests.post(url, json=create_cell)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
status = response.json()["status"]
|
|
55
|
+
|
|
56
|
+
except requests.exceptions.RequestException as e:
|
|
57
|
+
click.echo(f"Error sending request: {e}")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if status == True:
|
|
61
|
+
host = response.json()["host"]
|
|
62
|
+
cellkey = click.prompt(f"Please verify your email address with the Cell Key send to {email}")
|
|
63
|
+
|
|
64
|
+
url = f"https://{network}/api/verify_email"
|
|
65
|
+
|
|
66
|
+
verify_email = {"host": host, "email": email, "cellkey": cellkey}
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
response = requests.post(url, json=verify_email)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
status = response.json()["status"]
|
|
72
|
+
|
|
73
|
+
except requests.exceptions.RequestException as e:
|
|
74
|
+
click.echo(f"Error sending request: {e}")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if status == True:
|
|
78
|
+
synapse = response.json()["synapse"]
|
|
79
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
80
|
+
credentials_folder_path.mkdir(parents=True, exist_ok=True)
|
|
81
|
+
|
|
82
|
+
env_path = credentials_folder_path / ".env"
|
|
83
|
+
env_path.write_text(f"HOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
|
|
84
|
+
|
|
85
|
+
click.echo(f"Welcome to Neuronum! Community Cell '{host}' created and connected!")
|
|
86
|
+
|
|
87
|
+
if status == False:
|
|
88
|
+
click.echo(f"Error:'{email}' already assigned!")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@click.command()
|
|
92
|
+
def connect_cell():
|
|
93
|
+
email = click.prompt("Enter your Email")
|
|
94
|
+
password = click.prompt("Enter password", hide_input=True)
|
|
95
|
+
|
|
96
|
+
network = questionary.select(
|
|
97
|
+
"Choose Network:",
|
|
98
|
+
choices=["neuronum.net"]
|
|
99
|
+
).ask()
|
|
100
|
+
|
|
101
|
+
url = f"https://{network}/api/connect_cell"
|
|
102
|
+
payload = {"email": email, "password": password}
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
response = requests.post(url, json=payload)
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
status = response.json()["status"]
|
|
108
|
+
host = response.json()["host"]
|
|
109
|
+
except requests.exceptions.RequestException as e:
|
|
110
|
+
click.echo(f"Error connecting: {e}")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if status == True:
|
|
114
|
+
cellkey = click.prompt(f"Please verify your email address with the Cell Key send to {email}")
|
|
115
|
+
url = f"https://{network}/api/verify_email"
|
|
116
|
+
verify_email = {"host": host, "email": email, "cellkey": cellkey}
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
response = requests.post(url, json=verify_email)
|
|
120
|
+
response.raise_for_status()
|
|
121
|
+
status = response.json()["status"]
|
|
122
|
+
synapse = response.json()["synapse"]
|
|
123
|
+
|
|
124
|
+
except requests.exceptions.RequestException as e:
|
|
125
|
+
click.echo(f"Error sending request: {e}")
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if status == True:
|
|
129
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
130
|
+
credentials_folder_path.mkdir(parents=True, exist_ok=True)
|
|
131
|
+
|
|
132
|
+
env_path = credentials_folder_path / f".env"
|
|
133
|
+
env_path.write_text(f"HOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
|
|
134
|
+
|
|
135
|
+
click.echo(f"Cell '{host}' connected!")
|
|
136
|
+
else:
|
|
137
|
+
click.echo(f"Connection failed!")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@click.command()
|
|
141
|
+
def view_cell():
|
|
142
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
143
|
+
env_path = credentials_folder_path / ".env"
|
|
144
|
+
|
|
145
|
+
env_data = {}
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
with open(env_path, "r") as f:
|
|
149
|
+
for line in f:
|
|
150
|
+
key, value = line.strip().split("=")
|
|
151
|
+
env_data[key] = value
|
|
152
|
+
|
|
153
|
+
host = env_data.get("HOST", "")
|
|
154
|
+
|
|
155
|
+
except FileNotFoundError:
|
|
156
|
+
click.echo("Error: No credentials found. Please connect to a cell first.")
|
|
157
|
+
return
|
|
158
|
+
except Exception as e:
|
|
159
|
+
click.echo(f"Error reading .env file: {e}")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
if host:
|
|
163
|
+
click.echo(f"Connected Cell: '{host}'")
|
|
164
|
+
else:
|
|
165
|
+
click.echo("No active cell connection found.")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@click.command()
|
|
169
|
+
def disconnect_cell():
|
|
170
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
171
|
+
env_path = credentials_folder_path / ".env"
|
|
172
|
+
|
|
173
|
+
env_data = {}
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
with open(env_path, "r") as f:
|
|
177
|
+
for line in f:
|
|
178
|
+
key, value = line.strip().split("=")
|
|
179
|
+
env_data[key] = value
|
|
180
|
+
|
|
181
|
+
host = env_data.get("HOST", "")
|
|
182
|
+
|
|
183
|
+
except FileNotFoundError:
|
|
184
|
+
click.echo("Error: .env with credentials not found")
|
|
185
|
+
return
|
|
186
|
+
except Exception as e:
|
|
187
|
+
click.echo(f"Error reading .env file: {e}")
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
if env_path.exists():
|
|
191
|
+
if click.confirm(f"Are you sure you want to disconnect Cell '{host}'?", default=True):
|
|
192
|
+
os.remove(env_path)
|
|
193
|
+
click.echo(f"'{host}' disconnected!")
|
|
194
|
+
else:
|
|
195
|
+
click.echo("Disconnect canceled.")
|
|
196
|
+
else:
|
|
197
|
+
click.echo(f"No Neuronum Cell connected!")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@click.command()
|
|
201
|
+
def delete_cell():
|
|
202
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
203
|
+
env_path = credentials_folder_path / ".env"
|
|
204
|
+
|
|
205
|
+
env_data = {}
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
with open(env_path, "r") as f:
|
|
209
|
+
for line in f:
|
|
210
|
+
key, value = line.strip().split("=")
|
|
211
|
+
env_data[key] = value
|
|
212
|
+
|
|
213
|
+
host = env_data.get("HOST", "")
|
|
214
|
+
password = env_data.get("PASSWORD", "")
|
|
215
|
+
network = env_data.get("NETWORK", "")
|
|
216
|
+
synapse = env_data.get("SYNAPSE", "")
|
|
217
|
+
|
|
218
|
+
except FileNotFoundError:
|
|
219
|
+
click.echo("Error: No cell connected. Connect Cell first to delete")
|
|
220
|
+
return
|
|
221
|
+
except Exception as e:
|
|
222
|
+
click.echo(f"Error reading .env file: {e}")
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
confirm = click.confirm(f" Are you sure you want to delete '{host}'?", default=True)
|
|
226
|
+
if not confirm:
|
|
227
|
+
click.echo("Deletion canceled.")
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
url = f"https://{network}/api/delete_cell"
|
|
231
|
+
payload = {"host": host, "password": password, "synapse": synapse}
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
response = requests.delete(url, json=payload)
|
|
235
|
+
response.raise_for_status()
|
|
236
|
+
status = response.json()["status"]
|
|
237
|
+
except requests.exceptions.RequestException as e:
|
|
238
|
+
click.echo(f"Error deleting cell: {e}")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
if status == True:
|
|
242
|
+
env_path = credentials_folder_path / f"{host}.env"
|
|
243
|
+
if env_path.exists():
|
|
244
|
+
os.remove(env_path)
|
|
245
|
+
click.echo("Credentials deleted successfully!")
|
|
246
|
+
click.echo(f"Neuronum Cell '{host}' has been deleted!")
|
|
247
|
+
else:
|
|
248
|
+
click.echo(f"Neuronum Cell '{host}' deletion failed!")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@click.command()
|
|
252
|
+
@click.option('--sync', multiple=True, default=None, help="Optional stream IDs for sync.")
|
|
253
|
+
@click.option('--stream', multiple=True, default=None, help="Optional stream ID for stream.")
|
|
254
|
+
@click.option('--app', is_flag=True, help="Generate a Node with app template")
|
|
255
|
+
def init_node(sync, stream, app):
|
|
256
|
+
descr = click.prompt("Node description: Type up to 25 characters").strip()
|
|
257
|
+
if descr and len(descr) > 25:
|
|
258
|
+
click.echo("Description too long. Max 25 characters allowed.")
|
|
259
|
+
return
|
|
260
|
+
asyncio.run(async_init_node(sync, stream, app, descr))
|
|
261
|
+
|
|
262
|
+
async def async_init_node(sync, stream, app, descr):
|
|
263
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
264
|
+
env_path = credentials_folder_path / ".env"
|
|
265
|
+
|
|
266
|
+
env_data = {}
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
with open(env_path, "r") as f:
|
|
270
|
+
for line in f:
|
|
271
|
+
key, value = line.strip().split("=")
|
|
272
|
+
env_data[key] = value
|
|
273
|
+
|
|
274
|
+
host = env_data.get("HOST", "")
|
|
275
|
+
password = env_data.get("PASSWORD", "")
|
|
276
|
+
network = env_data.get("NETWORK", "")
|
|
277
|
+
synapse = env_data.get("SYNAPSE", "")
|
|
278
|
+
|
|
279
|
+
cell = neuronum.Cell(
|
|
280
|
+
host=host,
|
|
281
|
+
password=password,
|
|
282
|
+
network=network,
|
|
283
|
+
synapse=synapse
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
except FileNotFoundError:
|
|
287
|
+
click.echo("No cell connected. Connect your cell with command neuronum connect-cell")
|
|
288
|
+
return
|
|
289
|
+
except Exception as e:
|
|
290
|
+
click.echo(f"Error reading .env file: {e}")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
url = f"https://{network}/api/init_node"
|
|
294
|
+
node = {
|
|
295
|
+
"host": host,
|
|
296
|
+
"password": password,
|
|
297
|
+
"synapse": synapse,
|
|
298
|
+
"descr": descr,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async with aiohttp.ClientSession() as session:
|
|
302
|
+
try:
|
|
303
|
+
async with session.post(url, json=node) as response:
|
|
304
|
+
response.raise_for_status()
|
|
305
|
+
data = await response.json()
|
|
306
|
+
nodeID = data["nodeID"]
|
|
307
|
+
except aiohttp.ClientError as e:
|
|
308
|
+
click.echo(f"Error sending request: {e}")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
node_filename = "node_" + nodeID.replace("::node", "")
|
|
312
|
+
project_path = Path(node_filename)
|
|
313
|
+
project_path.mkdir(exist_ok=True)
|
|
314
|
+
|
|
315
|
+
env_path = project_path / ".env"
|
|
316
|
+
await asyncio.to_thread(env_path.write_text, f"NODE={nodeID}\nHOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
|
|
317
|
+
|
|
318
|
+
if app:
|
|
319
|
+
config_path = project_path / "config.json"
|
|
320
|
+
await asyncio.to_thread(config_path.write_text, """{
|
|
321
|
+
"data_gateways": [
|
|
322
|
+
{
|
|
323
|
+
"type": "stream",
|
|
324
|
+
"id": "id::stx",
|
|
325
|
+
"info": "provide a detailed description about the stream"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"type": "transmitter",
|
|
329
|
+
"id": "id::tx",
|
|
330
|
+
"info": "provide a detailed description about the transmitter"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
"type": "circuit",
|
|
334
|
+
"id": "id::ctx",
|
|
335
|
+
"info": "provide a detailed description about the circuit"
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
"""
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
nodemd_path = project_path / "NODE.md"
|
|
343
|
+
await asyncio.to_thread(nodemd_path.write_text, """### NODE.md: Create a detailed Markdown File on how to interact with this Node""")
|
|
344
|
+
|
|
345
|
+
stx = sync[0] if sync else (stream[0] if stream else host.replace("::cell", "::stx"))
|
|
346
|
+
|
|
347
|
+
if sync:
|
|
348
|
+
for stx in sync:
|
|
349
|
+
sync_path = project_path / f"sync_{stx.replace('::stx', '')}.py"
|
|
350
|
+
sync_path.write_text(f"""\
|
|
351
|
+
import asyncio
|
|
352
|
+
import neuronum
|
|
353
|
+
import os
|
|
354
|
+
from dotenv import load_dotenv
|
|
355
|
+
|
|
356
|
+
load_dotenv()
|
|
357
|
+
host = os.getenv("HOST")
|
|
358
|
+
password = os.getenv("PASSWORD")
|
|
359
|
+
network = os.getenv("NETWORK")
|
|
360
|
+
synapse = os.getenv("SYNAPSE")
|
|
361
|
+
|
|
362
|
+
cell = neuronum.Cell(
|
|
363
|
+
host=host,
|
|
364
|
+
password=password,
|
|
365
|
+
network=network,
|
|
366
|
+
synapse=synapse
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
async def main():
|
|
370
|
+
STX = "{stx}"
|
|
371
|
+
async for operation in cell.sync(STX):
|
|
372
|
+
label = operation.get("label")
|
|
373
|
+
data = operation.get("data")
|
|
374
|
+
ts = operation.get("time")
|
|
375
|
+
stxID = operation.get("stxID")
|
|
376
|
+
operator = operation.get("operator")
|
|
377
|
+
print(label, data, ts, stxID, operator)
|
|
378
|
+
|
|
379
|
+
asyncio.run(main())
|
|
380
|
+
""")
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if stream:
|
|
384
|
+
for stx in stream:
|
|
385
|
+
stream_path = project_path / f"stream_{stx.replace('::stx', '')}.py"
|
|
386
|
+
stream_path.write_text(f"""\
|
|
387
|
+
import asyncio
|
|
388
|
+
import neuronum
|
|
389
|
+
import os
|
|
390
|
+
from dotenv import load_dotenv
|
|
391
|
+
import time
|
|
392
|
+
|
|
393
|
+
load_dotenv()
|
|
394
|
+
host = os.getenv("HOST")
|
|
395
|
+
password = os.getenv("PASSWORD")
|
|
396
|
+
network = os.getenv("NETWORK")
|
|
397
|
+
synapse = os.getenv("SYNAPSE")
|
|
398
|
+
|
|
399
|
+
cell = neuronum.Cell(
|
|
400
|
+
host=host,
|
|
401
|
+
password=password,
|
|
402
|
+
network=network,
|
|
403
|
+
synapse=synapse
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
async def main():
|
|
407
|
+
STX = "{stx}"
|
|
408
|
+
label = "your_label"
|
|
409
|
+
|
|
410
|
+
while True:
|
|
411
|
+
data = {{
|
|
412
|
+
"key1": "value1",
|
|
413
|
+
"key2": "value2",
|
|
414
|
+
"key3": "value3",
|
|
415
|
+
}}
|
|
416
|
+
await cell.stream(label, data, STX)
|
|
417
|
+
time.sleep(5)
|
|
418
|
+
|
|
419
|
+
asyncio.run(main())
|
|
420
|
+
""")
|
|
421
|
+
|
|
422
|
+
if not sync and not stream and not app:
|
|
423
|
+
sync_path = project_path / f"sync_{stx.replace('::stx', '')}.py"
|
|
424
|
+
sync_path.write_text(f"""\
|
|
425
|
+
import asyncio
|
|
426
|
+
import neuronum
|
|
427
|
+
import os
|
|
428
|
+
from dotenv import load_dotenv
|
|
429
|
+
|
|
430
|
+
load_dotenv()
|
|
431
|
+
host = os.getenv("HOST")
|
|
432
|
+
password = os.getenv("PASSWORD")
|
|
433
|
+
network = os.getenv("NETWORK")
|
|
434
|
+
synapse = os.getenv("SYNAPSE")
|
|
435
|
+
|
|
436
|
+
cell = neuronum.Cell(
|
|
437
|
+
host=host,
|
|
438
|
+
password=password,
|
|
439
|
+
network=network,
|
|
440
|
+
synapse=synapse
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
async def main():
|
|
444
|
+
async for operation in cell.sync():
|
|
445
|
+
message = operation.get("data").get("message")
|
|
446
|
+
print(message)
|
|
447
|
+
|
|
448
|
+
asyncio.run(main())
|
|
449
|
+
""")
|
|
450
|
+
|
|
451
|
+
stream_path = project_path / f"stream_{stx.replace('::stx', '')}.py"
|
|
452
|
+
stream_path.write_text(f"""\
|
|
453
|
+
import asyncio
|
|
454
|
+
import neuronum
|
|
455
|
+
import os
|
|
456
|
+
from dotenv import load_dotenv
|
|
457
|
+
import time
|
|
458
|
+
|
|
459
|
+
load_dotenv()
|
|
460
|
+
host = os.getenv("HOST")
|
|
461
|
+
password = os.getenv("PASSWORD")
|
|
462
|
+
network = os.getenv("NETWORK")
|
|
463
|
+
synapse = os.getenv("SYNAPSE")
|
|
464
|
+
|
|
465
|
+
cell = neuronum.Cell(
|
|
466
|
+
host=host,
|
|
467
|
+
password=password,
|
|
468
|
+
network=network,
|
|
469
|
+
synapse=synapse
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
async def main():
|
|
473
|
+
label = "Welcome to Neuronum"
|
|
474
|
+
|
|
475
|
+
while True:
|
|
476
|
+
data = {{
|
|
477
|
+
"message": "Hello, Neuronum!"
|
|
478
|
+
}}
|
|
479
|
+
await cell.stream(label, data)
|
|
480
|
+
time.sleep(5)
|
|
481
|
+
|
|
482
|
+
asyncio.run(main())
|
|
483
|
+
""")
|
|
484
|
+
|
|
485
|
+
if app and nodeID:
|
|
486
|
+
|
|
487
|
+
descr = f"{nodeID} App"
|
|
488
|
+
partners = ["private"]
|
|
489
|
+
stxID = await cell.create_stx(descr, partners)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
descr = f"Greet {nodeID}"
|
|
493
|
+
key_values = {
|
|
494
|
+
"say": "hello",
|
|
495
|
+
}
|
|
496
|
+
STX = stxID
|
|
497
|
+
label = "say:hello"
|
|
498
|
+
partners = ["private"]
|
|
499
|
+
txID = await cell.create_tx(descr, key_values, STX, label, partners)
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
app_path = project_path / "app.py"
|
|
503
|
+
app_path.write_text(f"""\
|
|
504
|
+
import asyncio
|
|
505
|
+
import neuronum
|
|
506
|
+
import os
|
|
507
|
+
from dotenv import load_dotenv
|
|
508
|
+
|
|
509
|
+
load_dotenv()
|
|
510
|
+
host = os.getenv("HOST")
|
|
511
|
+
password = os.getenv("PASSWORD")
|
|
512
|
+
network = os.getenv("NETWORK")
|
|
513
|
+
synapse = os.getenv("SYNAPSE")
|
|
514
|
+
|
|
515
|
+
cell = neuronum.Cell(
|
|
516
|
+
host=host,
|
|
517
|
+
password=password,
|
|
518
|
+
network=network,
|
|
519
|
+
synapse=synapse
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
async def main():
|
|
523
|
+
STX = "{stxID}"
|
|
524
|
+
async for operation in cell.sync(STX):
|
|
525
|
+
txID = operation.get("txID")
|
|
526
|
+
client = operation.get("operator")
|
|
527
|
+
|
|
528
|
+
if txID == "{txID}":
|
|
529
|
+
data = {{
|
|
530
|
+
"json": f"Hello {{client}} from {nodeID}",
|
|
531
|
+
"html": f\"\"\"
|
|
532
|
+
<!DOCTYPE html>
|
|
533
|
+
<html>
|
|
534
|
+
<head>
|
|
535
|
+
<meta charset="UTF-8">
|
|
536
|
+
<title>Greeting Node</title>
|
|
537
|
+
</head>
|
|
538
|
+
<body>
|
|
539
|
+
<div class="card">
|
|
540
|
+
<h1>Hello, {{client}}</h1>
|
|
541
|
+
<p>Greetings from <span class="node">{nodeID}</span></p>
|
|
542
|
+
</div>
|
|
543
|
+
</body>
|
|
544
|
+
</html>
|
|
545
|
+
\"\"\"
|
|
546
|
+
|
|
547
|
+
}}
|
|
548
|
+
await cell.tx_response(txID, client, data)
|
|
549
|
+
|
|
550
|
+
asyncio.run(main())
|
|
551
|
+
""")
|
|
552
|
+
|
|
553
|
+
click.echo(f"Neuronum Node '{nodeID}' initialized!")
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
@click.command()
|
|
557
|
+
@click.option('--d', is_flag=True, help="Start node in detached mode")
|
|
558
|
+
def start_node(d):
|
|
559
|
+
pid_file = Path.cwd() / "status.txt"
|
|
560
|
+
system_name = platform.system()
|
|
561
|
+
active_pids = []
|
|
562
|
+
|
|
563
|
+
start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
564
|
+
|
|
565
|
+
if pid_file.exists():
|
|
566
|
+
try:
|
|
567
|
+
with open(pid_file, "r") as f:
|
|
568
|
+
pids = [int(line.strip()) for line in f if line.strip().isdigit()]
|
|
569
|
+
for pid in pids:
|
|
570
|
+
if system_name == "Windows":
|
|
571
|
+
if psutil.pid_exists(pid):
|
|
572
|
+
active_pids.append(pid)
|
|
573
|
+
else:
|
|
574
|
+
try:
|
|
575
|
+
os.kill(pid, 0)
|
|
576
|
+
active_pids.append(pid)
|
|
577
|
+
except OSError:
|
|
578
|
+
continue
|
|
579
|
+
except Exception as e:
|
|
580
|
+
click.echo(f"Failed to read PID file: {e}")
|
|
581
|
+
|
|
582
|
+
if active_pids:
|
|
583
|
+
click.echo(f"Node is already running. Active PIDs: {', '.join(map(str, active_pids))}")
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
click.echo("Starting Node...")
|
|
587
|
+
|
|
588
|
+
project_path = Path.cwd()
|
|
589
|
+
script_files = glob.glob("sync_*.py") + glob.glob("stream_*.py") + glob.glob("app.py")
|
|
590
|
+
processes = []
|
|
591
|
+
|
|
592
|
+
for script in script_files:
|
|
593
|
+
script_path = project_path / script
|
|
594
|
+
if script_path.exists():
|
|
595
|
+
python_cmd = "pythonw" if system_name == "Windows" else "python"
|
|
596
|
+
|
|
597
|
+
if d:
|
|
598
|
+
process = subprocess.Popen(
|
|
599
|
+
["nohup", python_cmd, str(script_path), "&"] if system_name != "Windows"
|
|
600
|
+
else [python_cmd, str(script_path)],
|
|
601
|
+
stdout=subprocess.DEVNULL,
|
|
602
|
+
stderr=subprocess.DEVNULL,
|
|
603
|
+
start_new_session=True
|
|
604
|
+
)
|
|
605
|
+
else:
|
|
606
|
+
process = subprocess.Popen(
|
|
607
|
+
[sys.executable, str(script_path)],
|
|
608
|
+
stdout=None,
|
|
609
|
+
stderr=None
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
processes.append(process.pid)
|
|
613
|
+
|
|
614
|
+
if not processes:
|
|
615
|
+
click.echo("Error: No valid node script found. Ensure the node is set up correctly.")
|
|
616
|
+
return
|
|
617
|
+
|
|
618
|
+
with open(pid_file, "w") as f:
|
|
619
|
+
f.write(f"Started at: {start_time}\n")
|
|
620
|
+
f.write("\n".join(map(str, processes)))
|
|
621
|
+
|
|
622
|
+
click.echo(f"Node started successfully with PIDs: {', '.join(map(str, processes))}")
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
@click.command()
|
|
626
|
+
def check_node():
|
|
627
|
+
click.echo("Checking Node status...")
|
|
628
|
+
|
|
629
|
+
env_data = {}
|
|
630
|
+
try:
|
|
631
|
+
with open(".env", "r") as f:
|
|
632
|
+
for line in f:
|
|
633
|
+
if "=" in line:
|
|
634
|
+
key, value = line.strip().split("=", 1)
|
|
635
|
+
env_data[key] = value
|
|
636
|
+
nodeID = env_data.get("NODE", "")
|
|
637
|
+
except FileNotFoundError:
|
|
638
|
+
click.echo("Error: .env with credentials not found")
|
|
639
|
+
return
|
|
640
|
+
except Exception as e:
|
|
641
|
+
click.echo(f"Error reading .env file: {e}")
|
|
642
|
+
return
|
|
643
|
+
|
|
644
|
+
pid_file = Path.cwd() / "status.txt"
|
|
645
|
+
|
|
646
|
+
if not pid_file.exists():
|
|
647
|
+
click.echo(f"Node {nodeID} is not running. Status file missing.")
|
|
648
|
+
return
|
|
649
|
+
|
|
650
|
+
try:
|
|
651
|
+
with open(pid_file, "r") as f:
|
|
652
|
+
lines = f.readlines()
|
|
653
|
+
timestamp_line = next((line for line in lines if line.startswith("Started at:")), None)
|
|
654
|
+
pids = [int(line.strip()) for line in lines if line.strip().isdigit()]
|
|
655
|
+
|
|
656
|
+
if timestamp_line:
|
|
657
|
+
click.echo(timestamp_line.strip())
|
|
658
|
+
start_time = datetime.strptime(timestamp_line.split(":", 1)[1].strip(), "%Y-%m-%d %H:%M:%S")
|
|
659
|
+
now = datetime.now()
|
|
660
|
+
uptime = now - start_time
|
|
661
|
+
click.echo(f"Uptime: {str(uptime).split('.')[0]}")
|
|
662
|
+
except Exception as e:
|
|
663
|
+
click.echo(f"Failed to read PID file: {e}")
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
system_name = platform.system()
|
|
667
|
+
running_pids = []
|
|
668
|
+
|
|
669
|
+
for pid in pids:
|
|
670
|
+
if system_name == "Windows":
|
|
671
|
+
if psutil.pid_exists(pid):
|
|
672
|
+
running_pids.append(pid)
|
|
673
|
+
else:
|
|
674
|
+
try:
|
|
675
|
+
os.kill(pid, 0)
|
|
676
|
+
running_pids.append(pid)
|
|
677
|
+
except OSError:
|
|
678
|
+
continue
|
|
679
|
+
|
|
680
|
+
if running_pids:
|
|
681
|
+
for pid in running_pids:
|
|
682
|
+
proc = psutil.Process(pid)
|
|
683
|
+
mem = proc.memory_info().rss / (1024 * 1024) # in MB
|
|
684
|
+
cpu = proc.cpu_percent(interval=0.1)
|
|
685
|
+
click.echo(f"PID {pid} → Memory: {mem:.2f} MB | CPU: {cpu:.1f}%")
|
|
686
|
+
click.echo(f"Node {nodeID} is running. Active PIDs: {', '.join(map(str, running_pids))}")
|
|
687
|
+
else:
|
|
688
|
+
click.echo(f"Node {nodeID} is not running.")
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
@click.command()
|
|
692
|
+
@click.option('--d', is_flag=True, help="Restart node in detached mode")
|
|
693
|
+
def restart_node(d):
|
|
694
|
+
pid_file = Path.cwd() / "status.txt"
|
|
695
|
+
system_name = platform.system()
|
|
696
|
+
|
|
697
|
+
start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
698
|
+
|
|
699
|
+
env_data = {}
|
|
700
|
+
try:
|
|
701
|
+
with open(".env", "r") as f:
|
|
702
|
+
for line in f:
|
|
703
|
+
key, value = line.strip().split("=")
|
|
704
|
+
env_data[key] = value
|
|
705
|
+
|
|
706
|
+
nodeID = env_data.get("NODE", "")
|
|
707
|
+
|
|
708
|
+
except FileNotFoundError:
|
|
709
|
+
print("Error: .env with credentials not found")
|
|
710
|
+
return
|
|
711
|
+
except Exception as e:
|
|
712
|
+
print(f"Error reading .env file: {e}")
|
|
713
|
+
return
|
|
714
|
+
|
|
715
|
+
if pid_file.exists():
|
|
716
|
+
try:
|
|
717
|
+
with open(pid_file, "r") as f:
|
|
718
|
+
pids = [int(line.strip()) for line in f if line.strip().isdigit()]
|
|
719
|
+
|
|
720
|
+
for pid in pids:
|
|
721
|
+
if system_name == "Windows":
|
|
722
|
+
if psutil.pid_exists(pid):
|
|
723
|
+
proc = psutil.Process(pid)
|
|
724
|
+
proc.terminate()
|
|
725
|
+
else:
|
|
726
|
+
try:
|
|
727
|
+
os.kill(pid, 15)
|
|
728
|
+
except OSError:
|
|
729
|
+
continue
|
|
730
|
+
|
|
731
|
+
pid_file.unlink()
|
|
732
|
+
|
|
733
|
+
click.echo(f"Terminated existing {nodeID} processes: {', '.join(map(str, pids))}")
|
|
734
|
+
|
|
735
|
+
except Exception as e:
|
|
736
|
+
click.echo(f"Failed to terminate processes: {e}")
|
|
737
|
+
return
|
|
738
|
+
else:
|
|
739
|
+
click.echo(f"Node {nodeID} is not running")
|
|
740
|
+
|
|
741
|
+
click.echo(f"Starting Node {nodeID}...")
|
|
742
|
+
project_path = Path.cwd()
|
|
743
|
+
script_files = glob.glob("sync_*.py") + glob.glob("stream_*.py") + glob.glob("app.py")
|
|
744
|
+
processes = []
|
|
745
|
+
|
|
746
|
+
python_cmd = "pythonw" if system_name == "Windows" else "python"
|
|
747
|
+
|
|
748
|
+
for script in script_files:
|
|
749
|
+
script_path = project_path / script
|
|
750
|
+
if script_path.exists():
|
|
751
|
+
if d:
|
|
752
|
+
process = subprocess.Popen(
|
|
753
|
+
["nohup", python_cmd, str(script_path), "&"] if system_name != "Windows"
|
|
754
|
+
else [python_cmd, str(script_path)],
|
|
755
|
+
stdout=subprocess.DEVNULL,
|
|
756
|
+
stderr=subprocess.DEVNULL,
|
|
757
|
+
start_new_session=True
|
|
758
|
+
)
|
|
759
|
+
else:
|
|
760
|
+
process = subprocess.Popen(
|
|
761
|
+
[sys.executable, str(script_path)],
|
|
762
|
+
stdout=None,
|
|
763
|
+
stderr=None
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
processes.append(process.pid)
|
|
767
|
+
|
|
768
|
+
if not processes:
|
|
769
|
+
click.echo("Error: No valid node script found.")
|
|
770
|
+
return
|
|
771
|
+
|
|
772
|
+
with open(pid_file, "w") as f:
|
|
773
|
+
f.write(f"Started at: {start_time}\n")
|
|
774
|
+
f.write("\n".join(map(str, processes)))
|
|
775
|
+
|
|
776
|
+
click.echo(f"Node {nodeID} started with new PIDs: {', '.join(map(str, processes))}")
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
@click.command()
|
|
780
|
+
def stop_node():
|
|
781
|
+
asyncio.run(async_stop_node())
|
|
782
|
+
|
|
783
|
+
async def async_stop_node():
|
|
784
|
+
click.echo("Stopping Node...")
|
|
785
|
+
|
|
786
|
+
node_pid_path = Path("status.txt")
|
|
787
|
+
|
|
788
|
+
env_data = {}
|
|
789
|
+
try:
|
|
790
|
+
with open(".env", "r") as f:
|
|
791
|
+
for line in f:
|
|
792
|
+
key, value = line.strip().split("=")
|
|
793
|
+
env_data[key] = value
|
|
794
|
+
|
|
795
|
+
nodeID = env_data.get("NODE", "")
|
|
796
|
+
|
|
797
|
+
except FileNotFoundError:
|
|
798
|
+
print("Error: .env with credentials not found")
|
|
799
|
+
return
|
|
800
|
+
except Exception as e:
|
|
801
|
+
print(f"Error reading .env file: {e}")
|
|
802
|
+
return
|
|
803
|
+
|
|
804
|
+
try:
|
|
805
|
+
with open("status.txt", "r") as f:
|
|
806
|
+
pids = [int(line.strip()) for line in f if line.strip().isdigit()]
|
|
807
|
+
|
|
808
|
+
system_name = platform.system()
|
|
809
|
+
|
|
810
|
+
for pid in pids:
|
|
811
|
+
try:
|
|
812
|
+
if system_name == "Windows":
|
|
813
|
+
await asyncio.to_thread(subprocess.run, ["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
814
|
+
else:
|
|
815
|
+
await asyncio.to_thread(os.kill, pid, 9)
|
|
816
|
+
except ProcessLookupError:
|
|
817
|
+
click.echo(f"Warning: Process {pid} already stopped or does not exist.")
|
|
818
|
+
|
|
819
|
+
await asyncio.to_thread(os.remove, node_pid_path)
|
|
820
|
+
click.echo(f"Node {nodeID} stopped successfully!")
|
|
821
|
+
|
|
822
|
+
except FileNotFoundError:
|
|
823
|
+
click.echo("Error: No active node process found.")
|
|
824
|
+
except subprocess.CalledProcessError:
|
|
825
|
+
click.echo("Error: Unable to stop some node processes.")
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
@click.command()
|
|
829
|
+
def update_node():
|
|
830
|
+
click.echo("Update your Node")
|
|
831
|
+
node_type = questionary.select(
|
|
832
|
+
"Who can view your Node?:",
|
|
833
|
+
choices=["public", "private", "partners"]
|
|
834
|
+
).ask()
|
|
835
|
+
partners = "None"
|
|
836
|
+
if node_type == "partners":
|
|
837
|
+
prompt_msg = (
|
|
838
|
+
"Enter the list of partners who can view this Node.\n"
|
|
839
|
+
"Format: partner::cell, partner::cell, partner::cell\n"
|
|
840
|
+
"Press Enter to leave the list unchanged"
|
|
841
|
+
)
|
|
842
|
+
partners = click.prompt(
|
|
843
|
+
prompt_msg,
|
|
844
|
+
default="None",
|
|
845
|
+
show_default=False
|
|
846
|
+
).strip()
|
|
847
|
+
descr = click.prompt(
|
|
848
|
+
"Update Node description: Type up to 25 characters, or press Enter to leave it unchanged",
|
|
849
|
+
default="None",
|
|
850
|
+
show_default=False
|
|
851
|
+
).strip()
|
|
852
|
+
if descr and len(descr) > 25:
|
|
853
|
+
click.echo("Description too long. Max 25 characters allowed.")
|
|
854
|
+
return
|
|
855
|
+
asyncio.run(async_update_node(node_type, descr, partners))
|
|
856
|
+
|
|
857
|
+
async def async_update_node(node_type: str, descr: str, partners:str) -> None:
|
|
858
|
+
env_data = {}
|
|
859
|
+
|
|
860
|
+
try:
|
|
861
|
+
with open(".env", "r") as f:
|
|
862
|
+
for line in f:
|
|
863
|
+
key, value = line.strip().split("=")
|
|
864
|
+
env_data[key] = value
|
|
865
|
+
|
|
866
|
+
nodeID = env_data.get("NODE", "")
|
|
867
|
+
host = env_data.get("HOST", "")
|
|
868
|
+
password = env_data.get("PASSWORD", "")
|
|
869
|
+
network = env_data.get("NETWORK", "")
|
|
870
|
+
synapse = env_data.get("SYNAPSE", "")
|
|
871
|
+
|
|
872
|
+
except FileNotFoundError:
|
|
873
|
+
click.echo("Error: .env with credentials not found")
|
|
874
|
+
return
|
|
875
|
+
except Exception as e:
|
|
876
|
+
click.echo(f"Error reading .env file: {e}")
|
|
877
|
+
return
|
|
878
|
+
|
|
879
|
+
try:
|
|
880
|
+
with open("NODE.md", "r") as f:
|
|
881
|
+
nodemd_file = f.read()
|
|
882
|
+
|
|
883
|
+
with open("config.json", "r") as f:
|
|
884
|
+
config_file = f.read()
|
|
885
|
+
|
|
886
|
+
except FileNotFoundError:
|
|
887
|
+
click.echo("Error: NODE.md file not found")
|
|
888
|
+
return
|
|
889
|
+
except Exception as e:
|
|
890
|
+
click.echo(f"Error reading NODE.md file: {e}")
|
|
891
|
+
return
|
|
892
|
+
|
|
893
|
+
if node_type == "partners":
|
|
894
|
+
node_type = partners
|
|
895
|
+
|
|
896
|
+
url = f"https://{network}/api/update_node"
|
|
897
|
+
node = {
|
|
898
|
+
"nodeID": nodeID,
|
|
899
|
+
"host": host,
|
|
900
|
+
"password": password,
|
|
901
|
+
"synapse": synapse,
|
|
902
|
+
"node_type": node_type,
|
|
903
|
+
"nodemd_file": nodemd_file,
|
|
904
|
+
"config_file": config_file,
|
|
905
|
+
"descr": descr,
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async with aiohttp.ClientSession() as session:
|
|
909
|
+
try:
|
|
910
|
+
async with session.post(url, json=node) as response:
|
|
911
|
+
response.raise_for_status()
|
|
912
|
+
data = await response.json()
|
|
913
|
+
nodeID = data["nodeID"]
|
|
914
|
+
node_url = data["node_url"]
|
|
915
|
+
except aiohttp.ClientError as e:
|
|
916
|
+
click.echo(f"Error sending request: {e}")
|
|
917
|
+
return
|
|
918
|
+
|
|
919
|
+
if node_type == "public":
|
|
920
|
+
click.echo(f"Neuronum Node '{nodeID}' updated! Visit: {node_url}")
|
|
921
|
+
else:
|
|
922
|
+
click.echo(f"Neuronum Node '{nodeID}' updated!")
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
@click.command()
|
|
926
|
+
def delete_node():
|
|
927
|
+
asyncio.run(async_delete_node())
|
|
928
|
+
|
|
929
|
+
async def async_delete_node():
|
|
930
|
+
env_data = {}
|
|
931
|
+
|
|
932
|
+
try:
|
|
933
|
+
with open(".env", "r") as f:
|
|
934
|
+
for line in f:
|
|
935
|
+
key, value = line.strip().split("=")
|
|
936
|
+
env_data[key] = value
|
|
937
|
+
|
|
938
|
+
nodeID = env_data.get("NODE", "")
|
|
939
|
+
host = env_data.get("HOST", "")
|
|
940
|
+
password = env_data.get("PASSWORD", "")
|
|
941
|
+
network = env_data.get("NETWORK", "")
|
|
942
|
+
synapse = env_data.get("SYNAPSE", "")
|
|
943
|
+
|
|
944
|
+
except FileNotFoundError:
|
|
945
|
+
click.echo("Error: .env with credentials not found")
|
|
946
|
+
return
|
|
947
|
+
except Exception as e:
|
|
948
|
+
click.echo(f"Error reading .env file: {e}")
|
|
949
|
+
return
|
|
950
|
+
|
|
951
|
+
url = f"https://{network}/api/delete_node"
|
|
952
|
+
node_payload = {
|
|
953
|
+
"nodeID": nodeID,
|
|
954
|
+
"host": host,
|
|
955
|
+
"password": password,
|
|
956
|
+
"synapse": synapse
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async with aiohttp.ClientSession() as session:
|
|
960
|
+
try:
|
|
961
|
+
async with session.post(url, json=node_payload) as response:
|
|
962
|
+
response.raise_for_status()
|
|
963
|
+
data = await response.json()
|
|
964
|
+
nodeID = data["nodeID"]
|
|
965
|
+
except aiohttp.ClientError as e:
|
|
966
|
+
click.echo(f"Error sending request: {e}")
|
|
967
|
+
return
|
|
968
|
+
|
|
969
|
+
click.echo(f"Neuronum Node '{nodeID}' deleted!")
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
@click.command()
|
|
973
|
+
@click.option('--tx', required=True, help="Transmitter ID")
|
|
974
|
+
@click.argument('kvpairs', nargs=-1)
|
|
975
|
+
def activate(tx, kvpairs):
|
|
976
|
+
try:
|
|
977
|
+
data = dict(pair.split(':', 1) for pair in kvpairs)
|
|
978
|
+
except ValueError:
|
|
979
|
+
click.echo("Invalid input. Use key:value pairs.")
|
|
980
|
+
return
|
|
981
|
+
|
|
982
|
+
asyncio.run(async_activate(tx, data))
|
|
983
|
+
|
|
984
|
+
async def async_activate(tx, data):
|
|
985
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
986
|
+
env_path = credentials_folder_path / ".env"
|
|
987
|
+
env_data = {}
|
|
988
|
+
|
|
989
|
+
try:
|
|
990
|
+
with open(env_path, "r") as f:
|
|
991
|
+
for line in f:
|
|
992
|
+
key, value = line.strip().split("=")
|
|
993
|
+
env_data[key] = value
|
|
994
|
+
except FileNotFoundError:
|
|
995
|
+
click.echo("No cell connected. Try: neuronum connect-cell")
|
|
996
|
+
return
|
|
997
|
+
except Exception as e:
|
|
998
|
+
click.echo(f"Error reading .env: {e}")
|
|
999
|
+
return
|
|
1000
|
+
|
|
1001
|
+
cell = neuronum.Cell(
|
|
1002
|
+
host=env_data.get("HOST", ""),
|
|
1003
|
+
password=env_data.get("PASSWORD", ""),
|
|
1004
|
+
network=env_data.get("NETWORK", ""),
|
|
1005
|
+
synapse=env_data.get("SYNAPSE", "")
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
tx_response = await cell.activate_tx(tx, data)
|
|
1009
|
+
click.echo(tx_response)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
@click.command()
|
|
1013
|
+
@click.option('--ctx', required=True, help="Circuit ID")
|
|
1014
|
+
@click.argument('label', nargs=-1)
|
|
1015
|
+
def load(ctx, label):
|
|
1016
|
+
if len(label) > 1 and all(Path(x).exists() for x in label):
|
|
1017
|
+
label = "*"
|
|
1018
|
+
else:
|
|
1019
|
+
label = " ".join(label)
|
|
1020
|
+
|
|
1021
|
+
asyncio.run(async_load(ctx, label))
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
async def async_load(ctx, label):
|
|
1025
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
1026
|
+
env_path = credentials_folder_path / ".env"
|
|
1027
|
+
env_data = {}
|
|
1028
|
+
|
|
1029
|
+
try:
|
|
1030
|
+
with open(env_path, "r") as f:
|
|
1031
|
+
for line in f:
|
|
1032
|
+
key, value = line.strip().split("=")
|
|
1033
|
+
env_data[key] = value
|
|
1034
|
+
except FileNotFoundError:
|
|
1035
|
+
click.echo("No cell connected. Try: neuronum connect-cell")
|
|
1036
|
+
return
|
|
1037
|
+
except Exception as e:
|
|
1038
|
+
click.echo(f"Error reading .env: {e}")
|
|
1039
|
+
return
|
|
1040
|
+
|
|
1041
|
+
cell = neuronum.Cell(
|
|
1042
|
+
host=env_data.get("HOST", ""),
|
|
1043
|
+
password=env_data.get("PASSWORD", ""),
|
|
1044
|
+
network=env_data.get("NETWORK", ""),
|
|
1045
|
+
synapse=env_data.get("SYNAPSE", "")
|
|
1046
|
+
)
|
|
1047
|
+
|
|
1048
|
+
data = await cell.load(label, ctx)
|
|
1049
|
+
click.echo(data)
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
@click.command()
|
|
1053
|
+
@click.option('--stx', default=None, help="Stream ID (optional)")
|
|
1054
|
+
def sync(stx):
|
|
1055
|
+
asyncio.run(async_sync(stx))
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
async def async_sync(stx):
|
|
1059
|
+
credentials_folder_path = Path.home() / ".neuronum"
|
|
1060
|
+
env_path = credentials_folder_path / ".env"
|
|
1061
|
+
env_data = {}
|
|
1062
|
+
|
|
1063
|
+
try:
|
|
1064
|
+
with open(env_path, "r") as f:
|
|
1065
|
+
for line in f:
|
|
1066
|
+
key, value = line.strip().split("=")
|
|
1067
|
+
env_data[key] = value
|
|
1068
|
+
except FileNotFoundError:
|
|
1069
|
+
click.echo("No cell connected. Try: neuronum connect-cell")
|
|
1070
|
+
return
|
|
1071
|
+
except Exception as e:
|
|
1072
|
+
click.echo(f"Error reading .env: {e}")
|
|
1073
|
+
return
|
|
1074
|
+
|
|
1075
|
+
cell = neuronum.Cell(
|
|
1076
|
+
host=env_data.get("HOST", ""),
|
|
1077
|
+
password=env_data.get("PASSWORD", ""),
|
|
1078
|
+
network=env_data.get("NETWORK", ""),
|
|
1079
|
+
synapse=env_data.get("SYNAPSE", "")
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
if stx:
|
|
1083
|
+
print(f"Listening to Stream '{stx}'! Close connection with CTRL+C")
|
|
1084
|
+
else:
|
|
1085
|
+
print(f"Listening to '{cell.host}' private Stream! Close connection with CTRL+C")
|
|
1086
|
+
async for operation in cell.sync() if stx is None else cell.sync(stx):
|
|
1087
|
+
label = operation.get("label")
|
|
1088
|
+
data = operation.get("data")
|
|
1089
|
+
ts = operation.get("time")
|
|
1090
|
+
stxID = operation.get("stxID")
|
|
1091
|
+
operator = operation.get("operator")
|
|
1092
|
+
txID = operation.get("txID")
|
|
1093
|
+
print(label, data, ts, operator, txID, stxID)
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
cli.add_command(create_cell)
|
|
1097
|
+
cli.add_command(connect_cell)
|
|
1098
|
+
cli.add_command(view_cell)
|
|
1099
|
+
cli.add_command(disconnect_cell)
|
|
1100
|
+
cli.add_command(delete_cell)
|
|
1101
|
+
cli.add_command(init_node)
|
|
1102
|
+
cli.add_command(start_node)
|
|
1103
|
+
cli.add_command(restart_node)
|
|
1104
|
+
cli.add_command(stop_node)
|
|
1105
|
+
cli.add_command(check_node)
|
|
1106
|
+
cli.add_command(update_node)
|
|
1107
|
+
cli.add_command(delete_node)
|
|
1108
|
+
cli.add_command(activate)
|
|
1109
|
+
cli.add_command(load)
|
|
1110
|
+
cli.add_command(sync)
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
if __name__ == "__main__":
|
|
1114
|
+
cli()
|