nexaroa 0.0.111__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.
Files changed (78) hide show
  1. neuroshard/__init__.py +93 -0
  2. neuroshard/__main__.py +4 -0
  3. neuroshard/cli.py +466 -0
  4. neuroshard/core/__init__.py +92 -0
  5. neuroshard/core/consensus/verifier.py +252 -0
  6. neuroshard/core/crypto/__init__.py +20 -0
  7. neuroshard/core/crypto/ecdsa.py +392 -0
  8. neuroshard/core/economics/__init__.py +52 -0
  9. neuroshard/core/economics/constants.py +387 -0
  10. neuroshard/core/economics/ledger.py +2111 -0
  11. neuroshard/core/economics/market.py +975 -0
  12. neuroshard/core/economics/wallet.py +168 -0
  13. neuroshard/core/governance/__init__.py +74 -0
  14. neuroshard/core/governance/proposal.py +561 -0
  15. neuroshard/core/governance/registry.py +545 -0
  16. neuroshard/core/governance/versioning.py +332 -0
  17. neuroshard/core/governance/voting.py +453 -0
  18. neuroshard/core/model/__init__.py +30 -0
  19. neuroshard/core/model/dynamic.py +4186 -0
  20. neuroshard/core/model/llm.py +905 -0
  21. neuroshard/core/model/registry.py +164 -0
  22. neuroshard/core/model/scaler.py +387 -0
  23. neuroshard/core/model/tokenizer.py +568 -0
  24. neuroshard/core/network/__init__.py +56 -0
  25. neuroshard/core/network/connection_pool.py +72 -0
  26. neuroshard/core/network/dht.py +130 -0
  27. neuroshard/core/network/dht_plan.py +55 -0
  28. neuroshard/core/network/dht_proof_store.py +516 -0
  29. neuroshard/core/network/dht_protocol.py +261 -0
  30. neuroshard/core/network/dht_service.py +506 -0
  31. neuroshard/core/network/encrypted_channel.py +141 -0
  32. neuroshard/core/network/nat.py +201 -0
  33. neuroshard/core/network/nat_traversal.py +695 -0
  34. neuroshard/core/network/p2p.py +929 -0
  35. neuroshard/core/network/p2p_data.py +150 -0
  36. neuroshard/core/swarm/__init__.py +106 -0
  37. neuroshard/core/swarm/aggregation.py +729 -0
  38. neuroshard/core/swarm/buffers.py +643 -0
  39. neuroshard/core/swarm/checkpoint.py +709 -0
  40. neuroshard/core/swarm/compute.py +624 -0
  41. neuroshard/core/swarm/diloco.py +844 -0
  42. neuroshard/core/swarm/factory.py +1288 -0
  43. neuroshard/core/swarm/heartbeat.py +669 -0
  44. neuroshard/core/swarm/logger.py +487 -0
  45. neuroshard/core/swarm/router.py +658 -0
  46. neuroshard/core/swarm/service.py +640 -0
  47. neuroshard/core/training/__init__.py +29 -0
  48. neuroshard/core/training/checkpoint.py +600 -0
  49. neuroshard/core/training/distributed.py +1602 -0
  50. neuroshard/core/training/global_tracker.py +617 -0
  51. neuroshard/core/training/production.py +276 -0
  52. neuroshard/governance_cli.py +729 -0
  53. neuroshard/grpc_server.py +895 -0
  54. neuroshard/runner.py +3223 -0
  55. neuroshard/sdk/__init__.py +92 -0
  56. neuroshard/sdk/client.py +990 -0
  57. neuroshard/sdk/errors.py +101 -0
  58. neuroshard/sdk/types.py +282 -0
  59. neuroshard/tracker/__init__.py +0 -0
  60. neuroshard/tracker/server.py +864 -0
  61. neuroshard/ui/__init__.py +0 -0
  62. neuroshard/ui/app.py +102 -0
  63. neuroshard/ui/templates/index.html +1052 -0
  64. neuroshard/utils/__init__.py +0 -0
  65. neuroshard/utils/autostart.py +81 -0
  66. neuroshard/utils/hardware.py +121 -0
  67. neuroshard/utils/serialization.py +90 -0
  68. neuroshard/version.py +1 -0
  69. nexaroa-0.0.111.dist-info/METADATA +283 -0
  70. nexaroa-0.0.111.dist-info/RECORD +78 -0
  71. nexaroa-0.0.111.dist-info/WHEEL +5 -0
  72. nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
  73. nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
  74. nexaroa-0.0.111.dist-info/top_level.txt +2 -0
  75. protos/__init__.py +0 -0
  76. protos/neuroshard.proto +651 -0
  77. protos/neuroshard_pb2.py +160 -0
  78. protos/neuroshard_pb2_grpc.py +1298 -0
@@ -0,0 +1,729 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ NeuroShard Governance CLI
4
+
5
+ Commands for participating in decentralized governance:
6
+ - List proposals
7
+ - View proposal details
8
+ - Create new proposals
9
+ - Vote on proposals
10
+ - Check voting results
11
+
12
+ Authentication:
13
+ - Uses wallet token (same as neuroshard-node)
14
+ - Token can be 64-char hex or 12-word mnemonic
15
+ - Derives ECDSA keypair for signing proposals/votes
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import sys
22
+ import time
23
+ from pathlib import Path
24
+
25
+ # Add parent to path for imports
26
+ sys.path.insert(0, str(Path(__file__).parent))
27
+
28
+ from neuroshard.version import __version__
29
+
30
+
31
+ # Lazy imports to speed up CLI startup
32
+ def get_governance_modules():
33
+ """Import governance modules lazily."""
34
+ from neuroshard.core.governance import (
35
+ NEP, NEPType, NEPStatus, create_proposal, EconomicImpact,
36
+ NEPRegistry, get_active_neps, get_pending_neps,
37
+ cast_vote, get_vote_tally, VoteResult,
38
+ )
39
+ from neuroshard.core.governance.voting import VoteChoice, VotingSystem
40
+ return {
41
+ 'NEP': NEP,
42
+ 'NEPType': NEPType,
43
+ 'NEPStatus': NEPStatus,
44
+ 'create_proposal': create_proposal,
45
+ 'EconomicImpact': EconomicImpact,
46
+ 'NEPRegistry': NEPRegistry,
47
+ 'get_active_neps': get_active_neps,
48
+ 'get_pending_neps': get_pending_neps,
49
+ 'cast_vote': cast_vote,
50
+ 'get_vote_tally': get_vote_tally,
51
+ 'VoteResult': VoteResult,
52
+ 'VoteChoice': VoteChoice,
53
+ 'VotingSystem': VotingSystem,
54
+ }
55
+
56
+
57
+ def get_crypto_module():
58
+ """Import crypto module lazily."""
59
+ from neuroshard.core.crypto.ecdsa import NodeCrypto, derive_keypair_from_token
60
+ return {
61
+ 'NodeCrypto': NodeCrypto,
62
+ 'derive_keypair_from_token': derive_keypair_from_token,
63
+ }
64
+
65
+
66
+ NEUROSHARD_DIR = os.path.expanduser("~/.neuroshard")
67
+ TOKEN_FILE = os.path.join(NEUROSHARD_DIR, "wallet_token")
68
+
69
+
70
+ def resolve_token(token_arg: str = None) -> str:
71
+ """
72
+ Resolve wallet token from various sources (in priority order):
73
+ 1. --token argument
74
+ 2. NEUROSHARD_TOKEN environment variable
75
+ 3. ~/.neuroshard/wallet_token file
76
+
77
+ Handles both 64-char hex and 12-word mnemonic formats.
78
+
79
+ Returns the resolved token or None.
80
+ """
81
+ token = None
82
+ source = None
83
+
84
+ # Priority 1: Command line argument
85
+ if token_arg:
86
+ token = token_arg
87
+ source = "command line"
88
+
89
+ # Priority 2: Environment variable
90
+ elif os.getenv("NEUROSHARD_TOKEN"):
91
+ token = os.getenv("NEUROSHARD_TOKEN")
92
+ source = "NEUROSHARD_TOKEN env"
93
+
94
+ # Priority 3: Token file
95
+ elif os.path.exists(TOKEN_FILE):
96
+ try:
97
+ with open(TOKEN_FILE, 'r') as f:
98
+ token = f.read().strip()
99
+ source = TOKEN_FILE
100
+ except Exception:
101
+ pass
102
+
103
+ if not token:
104
+ return None, None
105
+
106
+ # Handle mnemonic (12 words)
107
+ words = token.strip().split()
108
+ if len(words) == 12:
109
+ try:
110
+ from mnemonic import Mnemonic
111
+ mnemo = Mnemonic("english")
112
+ if mnemo.check(token):
113
+ seed = mnemo.to_seed(token, passphrase="")
114
+ token = seed[:32].hex()
115
+ source = f"{source} (mnemonic)"
116
+ except ImportError:
117
+ print("[WARNING] 'mnemonic' package not installed, treating as raw token")
118
+ except Exception as e:
119
+ print(f"[WARNING] Mnemonic error: {e}")
120
+
121
+ return token, source
122
+
123
+
124
+ def get_wallet(token: str):
125
+ """
126
+ Initialize wallet from token.
127
+
128
+ Returns (node_id, crypto) tuple where:
129
+ - node_id: Your unique identifier (derived from public key)
130
+ - crypto: NodeCrypto instance for signing
131
+ """
132
+ crypto_mod = get_crypto_module()
133
+ crypto = crypto_mod['NodeCrypto'](token)
134
+ return crypto.node_id, crypto
135
+
136
+
137
+ def save_token(token: str):
138
+ """Save token to file for future use."""
139
+ os.makedirs(NEUROSHARD_DIR, exist_ok=True)
140
+ with open(TOKEN_FILE, 'w') as f:
141
+ f.write(token)
142
+ os.chmod(TOKEN_FILE, 0o600) # Secure permissions
143
+ print(f"[INFO] Token saved to {TOKEN_FILE}")
144
+
145
+
146
+ def get_registry(ledger=None):
147
+ """Get or create the NEP registry."""
148
+ gov = get_governance_modules()
149
+ db_path = os.path.join(NEUROSHARD_DIR, "nep_registry.db")
150
+ os.makedirs(NEUROSHARD_DIR, exist_ok=True)
151
+ return gov['NEPRegistry'](db_path=db_path, ledger=ledger)
152
+
153
+
154
+ def get_voting_system(ledger=None):
155
+ """Get or create the voting system."""
156
+ gov = get_governance_modules()
157
+ db_path = os.path.join(NEUROSHARD_DIR, "nep_votes.db")
158
+ os.makedirs(NEUROSHARD_DIR, exist_ok=True)
159
+ return gov['VotingSystem'](db_path=db_path, ledger=ledger)
160
+
161
+
162
+ def require_wallet(args) -> tuple:
163
+ """
164
+ Ensure wallet is available for commands that need it.
165
+
166
+ Returns (node_id, crypto) or exits with error.
167
+ """
168
+ token, source = resolve_token(getattr(args, 'token', None))
169
+
170
+ if not token:
171
+ print("\nāŒ Wallet token required for this command!")
172
+ print()
173
+ print("Provide your token using one of:")
174
+ print(" 1. --token YOUR_TOKEN")
175
+ print(" 2. export NEUROSHARD_TOKEN=YOUR_TOKEN")
176
+ print(f" 3. echo YOUR_TOKEN > {TOKEN_FILE}")
177
+ print()
178
+ print("Get your token at: https://neuroshard.com/wallet")
179
+ sys.exit(1)
180
+
181
+ try:
182
+ node_id, crypto = get_wallet(token)
183
+ print(f"[WALLET] Authenticated as {node_id[:16]}... (from {source})")
184
+ return node_id, crypto
185
+ except Exception as e:
186
+ print(f"\nāŒ Failed to initialize wallet: {e}")
187
+ sys.exit(1)
188
+
189
+
190
+ def format_timestamp(ts):
191
+ """Format unix timestamp to readable string."""
192
+ if not ts:
193
+ return "N/A"
194
+ return time.strftime("%Y-%m-%d %H:%M", time.localtime(ts))
195
+
196
+
197
+ def format_status(status):
198
+ """Format status with color codes."""
199
+ colors = {
200
+ 'draft': '\033[90m', # Gray
201
+ 'review': '\033[94m', # Blue
202
+ 'voting': '\033[93m', # Yellow
203
+ 'approved': '\033[92m', # Green
204
+ 'rejected': '\033[91m', # Red
205
+ 'scheduled': '\033[95m', # Purple
206
+ 'active': '\033[92m', # Green
207
+ }
208
+ reset = '\033[0m'
209
+ color = colors.get(status, '')
210
+ return f"{color}{status.upper()}{reset}"
211
+
212
+
213
+ def cmd_list(args):
214
+ """List all proposals."""
215
+ gov = get_governance_modules()
216
+ registry = get_registry()
217
+
218
+ # Filter by status if specified
219
+ status_filter = None
220
+ if args.status:
221
+ try:
222
+ status_filter = gov['NEPStatus'](args.status)
223
+ except ValueError:
224
+ print(f"Invalid status: {args.status}")
225
+ print(f"Valid statuses: {[s.value for s in gov['NEPStatus']]}")
226
+ return 1
227
+
228
+ proposals = registry.list_proposals(status=status_filter, limit=args.limit)
229
+
230
+ if not proposals:
231
+ print("No proposals found.")
232
+ return 0
233
+
234
+ print(f"\n{'ID':<10} {'Status':<12} {'Type':<8} {'Title':<40} {'Created':<16}")
235
+ print("=" * 90)
236
+
237
+ for nep in proposals:
238
+ print(
239
+ f"{nep.nep_id:<10} "
240
+ f"{format_status(nep.status.value):<22} " # Extra space for color codes
241
+ f"{nep.nep_type.value:<8} "
242
+ f"{nep.title[:38]:<40} "
243
+ f"{format_timestamp(nep.created_at):<16}"
244
+ )
245
+
246
+ print(f"\nTotal: {len(proposals)} proposals")
247
+ return 0
248
+
249
+
250
+ def cmd_show(args):
251
+ """Show details of a specific proposal."""
252
+ gov = get_governance_modules()
253
+ registry = get_registry()
254
+ voting = get_voting_system()
255
+
256
+ nep = registry.get_proposal(args.nep_id)
257
+ if not nep:
258
+ print(f"Proposal not found: {args.nep_id}")
259
+ return 1
260
+
261
+ print(f"\n{'='*70}")
262
+ print(f" {nep.nep_id}: {nep.title}")
263
+ print(f"{'='*70}")
264
+ print()
265
+ print(f" Status: {format_status(nep.status.value)}")
266
+ print(f" Type: {nep.nep_type.value}")
267
+ print(f" Author: {nep.author_node_id[:20]}...")
268
+ print(f" Created: {format_timestamp(nep.created_at)}")
269
+ print(f" Updated: {format_timestamp(nep.updated_at)}")
270
+ print()
271
+ print(f" ABSTRACT")
272
+ print(f" {'-'*60}")
273
+ print(f" {nep.abstract}")
274
+ print()
275
+
276
+ # Economic impact
277
+ impact = nep.economic_impact
278
+ print(f" ECONOMIC IMPACT")
279
+ print(f" {'-'*60}")
280
+ print(f" Training efficiency: {impact.training_efficiency_multiplier:.1f}x")
281
+ print(f" Training reward: {impact.training_reward_multiplier:.1f}x")
282
+ print(f" Inference reward: {impact.inference_reward_multiplier:.1f}x")
283
+ print(f" Memory change: {impact.min_memory_change_mb:+d} MB")
284
+ print(f" Net earnings: {impact.net_earnings_change_percent:+.1f}%")
285
+ print()
286
+
287
+ # Voting info
288
+ if nep.status == gov['NEPStatus'].VOTING:
289
+ print(f" VOTING (Active)")
290
+ print(f" {'-'*60}")
291
+ print(f" Started: {format_timestamp(nep.voting_start)}")
292
+ print(f" Ends: {format_timestamp(nep.voting_end)}")
293
+
294
+ # Get tally
295
+ result = voting.get_vote_tally(nep.nep_id, registry)
296
+ print(f" Yes votes: {result.yes_count} ({result.yes_stake:.2f} NEURO)")
297
+ print(f" No votes: {result.no_count} ({result.no_stake:.2f} NEURO)")
298
+ print(f" Abstain: {result.abstain_count} ({result.abstain_stake:.2f} NEURO)")
299
+ print(f" Quorum: {result.participation_rate*100:.1f}% of {result.total_network_stake:.2f} NEURO")
300
+ if result.quorum_reached:
301
+ print(f" Approval: {result.approval_rate*100:.1f}% (threshold: 66%)")
302
+ print()
303
+
304
+ if args.full:
305
+ print(f" MOTIVATION")
306
+ print(f" {'-'*60}")
307
+ print(f" {nep.motivation}")
308
+ print()
309
+ print(f" SPECIFICATION")
310
+ print(f" {'-'*60}")
311
+ print(f" {nep.specification}")
312
+ print()
313
+
314
+ return 0
315
+
316
+
317
+ def cmd_propose(args):
318
+ """Create a new proposal."""
319
+ gov = get_governance_modules()
320
+
321
+ # Require wallet authentication
322
+ node_id, crypto = require_wallet(args)
323
+
324
+ registry = get_registry()
325
+
326
+ # Parse type
327
+ try:
328
+ nep_type = gov['NEPType'](args.type)
329
+ except ValueError:
330
+ print(f"Invalid type: {args.type}")
331
+ print(f"Valid types: {[t.value for t in gov['NEPType']]}")
332
+ return 1
333
+
334
+ # If using a file for content
335
+ if args.file:
336
+ with open(args.file) as f:
337
+ content = json.load(f)
338
+ title = content.get('title', args.title)
339
+ abstract = content.get('abstract', '')
340
+ motivation = content.get('motivation', '')
341
+ specification = content.get('specification', '')
342
+ else:
343
+ title = args.title
344
+ abstract = args.abstract or input("Enter abstract (1-2 sentences): ")
345
+ motivation = args.motivation or input("Enter motivation (why is this needed?): ")
346
+ specification = args.specification or "See discussion thread."
347
+
348
+ # Create proposal
349
+ nep = gov['create_proposal'](
350
+ title=title,
351
+ nep_type=nep_type,
352
+ abstract=abstract,
353
+ motivation=motivation,
354
+ specification=specification,
355
+ author_node_id=node_id,
356
+ economic_impact=gov['EconomicImpact'](
357
+ net_earnings_change_percent=args.earnings_impact or 0.0
358
+ ),
359
+ )
360
+
361
+ # Confirm submission
362
+ print(f"\n{'='*60}")
363
+ print(f" PROPOSAL PREVIEW")
364
+ print(f"{'='*60}")
365
+ print(f" Title: {title}")
366
+ print(f" Type: {nep_type.value}")
367
+ print(f" Author: {node_id[:16]}...")
368
+ print(f" Abstract: {abstract[:100]}...")
369
+ print()
370
+ print(f" āš ļø COSTS:")
371
+ print(f" - 10 NEURO proposal fee (held in escrow)")
372
+ print(f" - Requires 100 NEURO staked")
373
+ print()
374
+ print(f" šŸ’° REWARDS (stake-proportional):")
375
+ print(f" - If approved: 0.1% of total stake voted + fee refund")
376
+ print(f" - If rejected w/ quorum: 0.01% of total stake voted")
377
+ print(f" - Example: 100K stake votes → ~110 NEURO if approved")
378
+ print()
379
+
380
+ if not args.yes:
381
+ confirm = input("Submit this proposal? [y/N]: ")
382
+ if confirm.lower() != 'y':
383
+ print("Cancelled.")
384
+ return 0
385
+
386
+ # Sign the proposal with ECDSA
387
+ signature = crypto.sign(nep.content_hash)
388
+
389
+ # Submit to registry
390
+ success, message, nep_id = registry.submit_proposal(
391
+ nep=nep,
392
+ node_id=node_id,
393
+ signature=signature,
394
+ )
395
+
396
+ if success:
397
+ print(f"\nāœ… {message}")
398
+ print(f" Your proposal ID: {nep_id}")
399
+ print(f" Your wallet: {node_id}")
400
+ print(f" Track status: neuroshard-governance show {nep_id}")
401
+ else:
402
+ print(f"\nāŒ Failed: {message}")
403
+ return 1
404
+
405
+ return 0
406
+
407
+
408
+ def cmd_vote(args):
409
+ """Vote on a proposal."""
410
+ gov = get_governance_modules()
411
+
412
+ # Require wallet authentication
413
+ node_id, crypto = require_wallet(args)
414
+
415
+ registry = get_registry()
416
+ voting = get_voting_system()
417
+
418
+ # Check proposal exists and is in voting
419
+ nep = registry.get_proposal(args.nep_id)
420
+ if not nep:
421
+ print(f"Proposal not found: {args.nep_id}")
422
+ return 1
423
+
424
+ if nep.status != gov['NEPStatus'].VOTING:
425
+ print(f"Proposal is not in voting phase (status: {nep.status.value})")
426
+ return 1
427
+
428
+ # Parse vote choice
429
+ choice_map = {
430
+ 'yes': gov['VoteChoice'].YES,
431
+ 'no': gov['VoteChoice'].NO,
432
+ 'abstain': gov['VoteChoice'].ABSTAIN,
433
+ }
434
+ choice = choice_map.get(args.choice.lower())
435
+ if not choice:
436
+ print(f"Invalid vote: {args.choice}")
437
+ print("Valid choices: yes, no, abstain")
438
+ return 1
439
+
440
+ # Show proposal summary
441
+ print(f"\n{'='*60}")
442
+ print(f" VOTING: {nep.nep_id}")
443
+ print(f"{'='*60}")
444
+ print(f" Title: {nep.title}")
445
+ print(f" Impact: {nep.economic_impact.describe()}")
446
+ print(f" Ends: {format_timestamp(nep.voting_end)}")
447
+ print()
448
+ print(f" Your wallet: {node_id[:16]}...")
449
+ print(f" Your vote: {args.choice.upper()}")
450
+ if args.reason:
451
+ print(f" Reason: {args.reason}")
452
+ print()
453
+ print(f" šŸ’° You will earn: 0.01% of your staked NEURO for voting")
454
+ print()
455
+
456
+ if not args.yes:
457
+ confirm = input("Confirm vote? [y/N]: ")
458
+ if confirm.lower() != 'y':
459
+ print("Cancelled.")
460
+ return 0
461
+
462
+ # Sign the vote with ECDSA
463
+ vote_payload = f"{args.nep_id}:{node_id}:{choice.value}:{int(time.time())}"
464
+ signature = crypto.sign(vote_payload)
465
+
466
+ # Cast vote
467
+ success, message = voting.cast_vote(
468
+ nep_id=args.nep_id,
469
+ voter_node_id=node_id,
470
+ choice=choice,
471
+ signature=signature,
472
+ reason=args.reason or "",
473
+ nep_registry=registry,
474
+ )
475
+
476
+ if success:
477
+ print(f"\nāœ… {message}")
478
+ print(f" Your vote has been recorded and signed.")
479
+ else:
480
+ print(f"\nāŒ Failed: {message}")
481
+ return 1
482
+
483
+ return 0
484
+
485
+
486
+ def cmd_results(args):
487
+ """Show voting results for a proposal."""
488
+ gov = get_governance_modules()
489
+ registry = get_registry()
490
+ voting = get_voting_system()
491
+
492
+ nep = registry.get_proposal(args.nep_id)
493
+ if not nep:
494
+ print(f"Proposal not found: {args.nep_id}")
495
+ return 1
496
+
497
+ result = voting.get_vote_tally(args.nep_id, registry)
498
+
499
+ print(f"\n{'='*60}")
500
+ print(f" VOTING RESULTS: {nep.nep_id}")
501
+ print(f"{'='*60}")
502
+ print(f" {nep.title}")
503
+ print(f" Status: {format_status(nep.status.value)}")
504
+ print()
505
+
506
+ # Vote counts
507
+ total_votes = result.yes_count + result.no_count + result.abstain_count
508
+ print(f" VOTES ({total_votes} total)")
509
+ print(f" {'-'*50}")
510
+
511
+ # Bar chart
512
+ if total_votes > 0:
513
+ yes_pct = result.yes_count / total_votes * 100
514
+ no_pct = result.no_count / total_votes * 100
515
+ abs_pct = result.abstain_count / total_votes * 100
516
+
517
+ bar_width = 30
518
+ yes_bar = 'ā–ˆ' * int(yes_pct / 100 * bar_width)
519
+ no_bar = 'ā–ˆ' * int(no_pct / 100 * bar_width)
520
+
521
+ print(f" YES: {result.yes_count:4} ({yes_pct:5.1f}%) \033[92m{yes_bar}\033[0m")
522
+ print(f" NO: {result.no_count:4} ({no_pct:5.1f}%) \033[91m{no_bar}\033[0m")
523
+ print(f" ABSTAIN: {result.abstain_count:4} ({abs_pct:5.1f}%)")
524
+ else:
525
+ print(f" No votes cast yet")
526
+ print()
527
+
528
+ # Stake-weighted results
529
+ print(f" STAKE-WEIGHTED")
530
+ print(f" {'-'*50}")
531
+ print(f" Yes stake: {result.yes_stake:12.2f} NEURO")
532
+ print(f" No stake: {result.no_stake:12.2f} NEURO")
533
+ print(f" Abstain stake: {result.abstain_stake:12.2f} NEURO")
534
+ print(f" Total voted: {result.total_stake_voted:12.2f} NEURO")
535
+ print(f" Network stake: {result.total_network_stake:12.2f} NEURO")
536
+ print()
537
+
538
+ # Thresholds
539
+ print(f" THRESHOLDS")
540
+ print(f" {'-'*50}")
541
+ quorum_status = "āœ…" if result.quorum_reached else "āŒ"
542
+ print(f" Quorum (20%): {result.participation_rate*100:5.1f}% {quorum_status}")
543
+
544
+ approval_status = "āœ…" if result.approval_rate >= 0.66 else "āŒ"
545
+ print(f" Approval (66%): {result.approval_rate*100:5.1f}% {approval_status}")
546
+ print()
547
+
548
+ if result.quorum_reached and result.approval_rate >= 0.66:
549
+ print(f" \033[92māœ“ Proposal PASSING\033[0m")
550
+ elif result.quorum_reached:
551
+ print(f" \033[91māœ— Proposal FAILING (below approval threshold)\033[0m")
552
+ else:
553
+ print(f" \033[93mā³ Waiting for quorum\033[0m")
554
+
555
+ return 0
556
+
557
+
558
+ def main():
559
+ """Main CLI entry point."""
560
+ parser = argparse.ArgumentParser(
561
+ description="NeuroShard Governance CLI",
562
+ formatter_class=argparse.RawDescriptionHelpFormatter,
563
+ epilog="""
564
+ WALLET SETUP:
565
+ Commands that modify state (propose, vote) require your wallet token.
566
+ This is the same token used for neuroshard-node.
567
+
568
+ Option 1: Pass directly
569
+ neuroshard-governance --token YOUR_TOKEN propose ...
570
+
571
+ Option 2: Environment variable
572
+ export NEUROSHARD_TOKEN=YOUR_TOKEN
573
+ neuroshard-governance propose ...
574
+
575
+ Option 3: Save to file (recommended)
576
+ neuroshard-governance --token YOUR_TOKEN wallet --save
577
+ # Future commands auto-use ~/.neuroshard/wallet_token
578
+
579
+ EXAMPLES:
580
+ # Check your wallet
581
+ neuroshard-governance --token YOUR_TOKEN wallet
582
+
583
+ # List all proposals (no auth needed)
584
+ neuroshard-governance list
585
+
586
+ # List proposals in voting
587
+ neuroshard-governance list --status voting
588
+
589
+ # View a proposal
590
+ neuroshard-governance show NEP-001
591
+
592
+ # Create a proposal (requires wallet)
593
+ neuroshard-governance propose --title "Add MTP Training" --type train
594
+
595
+ # Vote on a proposal (requires wallet)
596
+ neuroshard-governance vote NEP-001 yes --reason "Improves efficiency"
597
+
598
+ # Check voting results
599
+ neuroshard-governance results NEP-001
600
+
601
+ Get your wallet token at: https://neuroshard.com/wallet
602
+ """
603
+ )
604
+
605
+ parser.add_argument(
606
+ "--version", action="version",
607
+ version=f"NeuroShard Governance CLI {__version__}"
608
+ )
609
+
610
+ # Global token option (for commands that need authentication)
611
+ parser.add_argument(
612
+ "--token", type=str, default=None,
613
+ help="Wallet token (64-char hex or 12-word mnemonic). "
614
+ "Can also be set via NEUROSHARD_TOKEN env or ~/.neuroshard/wallet_token"
615
+ )
616
+
617
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
618
+
619
+ # List command (no auth required)
620
+ list_parser = subparsers.add_parser("list", help="List proposals")
621
+ list_parser.add_argument("--status", type=str, help="Filter by status")
622
+ list_parser.add_argument("--limit", type=int, default=50, help="Max results")
623
+
624
+ # Show command (no auth required)
625
+ show_parser = subparsers.add_parser("show", help="Show proposal details")
626
+ show_parser.add_argument("nep_id", type=str, help="Proposal ID (e.g., NEP-001)")
627
+ show_parser.add_argument("--full", action="store_true", help="Show full specification")
628
+
629
+ # Propose command (requires wallet)
630
+ propose_parser = subparsers.add_parser("propose", help="Create a proposal (requires wallet)")
631
+ propose_parser.add_argument("--title", type=str, required=True, help="Proposal title")
632
+ propose_parser.add_argument("--type", type=str, required=True,
633
+ help="Type: arch, econ, train, net, gov, emergency")
634
+ propose_parser.add_argument("--abstract", type=str, help="Brief description")
635
+ propose_parser.add_argument("--motivation", type=str, help="Why is this needed")
636
+ propose_parser.add_argument("--specification", type=str, help="Technical details")
637
+ propose_parser.add_argument("--file", type=str, help="JSON file with proposal content")
638
+ propose_parser.add_argument("--earnings-impact", type=float, help="Net earnings change %")
639
+ propose_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
640
+
641
+ # Vote command (requires wallet)
642
+ vote_parser = subparsers.add_parser("vote", help="Vote on a proposal (requires wallet)")
643
+ vote_parser.add_argument("nep_id", type=str, help="Proposal ID")
644
+ vote_parser.add_argument("choice", type=str, help="Vote: yes, no, abstain")
645
+ vote_parser.add_argument("--reason", type=str, help="Reason for your vote")
646
+ vote_parser.add_argument("-y", "--yes", action="store_true", help="Skip confirmation")
647
+
648
+ # Results command (no auth required)
649
+ results_parser = subparsers.add_parser("results", help="Show voting results")
650
+ results_parser.add_argument("nep_id", type=str, help="Proposal ID")
651
+
652
+ # Wallet command (utility)
653
+ wallet_parser = subparsers.add_parser("wallet", help="Show wallet info")
654
+ wallet_parser.add_argument("--save", action="store_true",
655
+ help="Save token to ~/.neuroshard/wallet_token")
656
+
657
+ args = parser.parse_args()
658
+
659
+ if not args.command:
660
+ parser.print_help()
661
+ return 0
662
+
663
+ # Dispatch to command handler
664
+ commands = {
665
+ 'list': cmd_list,
666
+ 'show': cmd_show,
667
+ 'propose': cmd_propose,
668
+ 'vote': cmd_vote,
669
+ 'results': cmd_results,
670
+ 'wallet': cmd_wallet,
671
+ }
672
+
673
+ handler = commands.get(args.command)
674
+ if handler:
675
+ return handler(args)
676
+ else:
677
+ parser.print_help()
678
+ return 1
679
+
680
+
681
+ def cmd_wallet(args):
682
+ """Show wallet info and optionally save token."""
683
+ token, source = resolve_token(args.token)
684
+
685
+ if not token:
686
+ print("\nāŒ No wallet token found!")
687
+ print()
688
+ print("Provide your token using one of:")
689
+ print(" 1. --token YOUR_TOKEN")
690
+ print(" 2. export NEUROSHARD_TOKEN=YOUR_TOKEN")
691
+ print(f" 3. echo YOUR_TOKEN > {TOKEN_FILE}")
692
+ print()
693
+ print("Get your token at: https://neuroshard.com/wallet")
694
+ return 1
695
+
696
+ try:
697
+ node_id, crypto = get_wallet(token)
698
+
699
+ print(f"\n{'='*60}")
700
+ print(f" WALLET INFO")
701
+ print(f"{'='*60}")
702
+ print(f" Node ID: {node_id}")
703
+ print(f" Public Key: {crypto.get_public_key_hex()[:32]}...")
704
+ print(f" Source: {source}")
705
+ print()
706
+
707
+ # Test signing
708
+ test_msg = "test_message"
709
+ sig = crypto.sign(test_msg)
710
+ verified = crypto.verify(test_msg, sig)
711
+ print(f" Signature test: {'āœ… OK' if verified else 'āŒ FAILED'}")
712
+ print()
713
+
714
+ if args.save:
715
+ save_token(token)
716
+ print(f" āœ… Token saved to {TOKEN_FILE}")
717
+ print(f" Future commands will use this automatically.")
718
+ else:
719
+ print(f" šŸ’” Run with --save to remember this wallet")
720
+
721
+ return 0
722
+
723
+ except Exception as e:
724
+ print(f"\nāŒ Failed to initialize wallet: {e}")
725
+ return 1
726
+
727
+
728
+ if __name__ == "__main__":
729
+ sys.exit(main())