hippius 0.1.13__py3-none-any.whl → 0.2.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.
hippius_sdk/cli.py CHANGED
@@ -7,9 +7,11 @@ utilities for encryption key generation, file operations, and marketplace intera
7
7
  """
8
8
 
9
9
  import argparse
10
+ import asyncio
10
11
  import base64
11
12
  import concurrent.futures
12
13
  import getpass
14
+ import inspect
13
15
  import json
14
16
  import os
15
17
  import random
@@ -181,12 +183,12 @@ def create_client(args):
181
183
  return client
182
184
 
183
185
 
184
- def handle_download(client, cid, output_path, decrypt=None):
186
+ async def handle_download(client, cid, output_path, decrypt=None):
185
187
  """Handle the download command"""
186
188
  print(f"Downloading {cid} to {output_path}...")
187
189
 
188
190
  # Use the enhanced download method which returns formatted information
189
- result = client.download_file(cid, output_path, decrypt=decrypt)
191
+ result = await client.download_file(cid, output_path, decrypt=decrypt)
190
192
 
191
193
  print(f"Download successful in {result['elapsed_seconds']} seconds!")
192
194
  print(f"Saved to: {result['output_path']}")
@@ -198,10 +200,10 @@ def handle_download(client, cid, output_path, decrypt=None):
198
200
  return 0
199
201
 
200
202
 
201
- def handle_exists(client, cid):
203
+ async def handle_exists(client, cid):
202
204
  """Handle the exists command"""
203
205
  print(f"Checking if CID {cid} exists on IPFS...")
204
- result = client.exists(cid)
206
+ result = await client.exists(cid)
205
207
 
206
208
  # Use the formatted CID from the result
207
209
  formatted_cid = result["formatted_cid"]
@@ -217,12 +219,12 @@ def handle_exists(client, cid):
217
219
  return 0
218
220
 
219
221
 
220
- def handle_cat(client, cid, max_size, decrypt=None):
222
+ async def handle_cat(client, cid, max_size, decrypt=None):
221
223
  """Handle the cat command"""
222
224
  print(f"Retrieving content of CID {cid}...")
223
225
  try:
224
226
  # Use the enhanced cat method with formatting
225
- result = client.cat(cid, max_display_bytes=max_size, decrypt=decrypt)
227
+ result = await client.cat(cid, max_display_bytes=max_size, decrypt=decrypt)
226
228
 
227
229
  # Display file information
228
230
  print(
@@ -255,7 +257,7 @@ def handle_cat(client, cid, max_size, decrypt=None):
255
257
  return 0
256
258
 
257
259
 
258
- def handle_store(client, file_path, miner_ids, encrypt=None):
260
+ async def handle_store(client, file_path, miner_ids, encrypt=None):
259
261
  """Handle the store command"""
260
262
  if not os.path.exists(file_path):
261
263
  print(f"Error: File {file_path} not found")
@@ -265,7 +267,7 @@ def handle_store(client, file_path, miner_ids, encrypt=None):
265
267
  start_time = time.time()
266
268
 
267
269
  # Use the enhanced upload_file method that returns formatted information
268
- result = client.upload_file(file_path, encrypt=encrypt)
270
+ result = await client.upload_file(file_path, encrypt=encrypt)
269
271
 
270
272
  ipfs_elapsed_time = time.time() - start_time
271
273
 
@@ -282,11 +284,23 @@ def handle_store(client, file_path, miner_ids, encrypt=None):
282
284
  start_time = time.time()
283
285
 
284
286
  try:
287
+ # Check if we have credits
288
+ try:
289
+ if hasattr(client.substrate_client, "get_free_credits"):
290
+ credits = client.substrate_client.get_free_credits()
291
+ print(f"Account credits: {credits}")
292
+ if credits <= 0:
293
+ print(
294
+ f"Warning: Account has no free credits (current: {credits}). Transaction may fail."
295
+ )
296
+ except Exception as e:
297
+ print(f"Warning: Could not check free credits: {e}")
298
+
285
299
  # Create a file input object for the marketplace
286
300
  file_input = {"fileHash": result["cid"], "fileName": result["filename"]}
287
301
 
288
- # Store on Substrate
289
- client.substrate_client.storage_request([file_input], miner_ids)
302
+ # Store on Substrate - now it's an async call
303
+ tx_hash = await client.substrate_client.storage_request([file_input], miner_ids)
290
304
 
291
305
  substrate_elapsed_time = time.time() - start_time
292
306
  print(
@@ -307,7 +321,7 @@ def handle_store(client, file_path, miner_ids, encrypt=None):
307
321
  return 0
308
322
 
309
323
 
310
- def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
324
+ async def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
311
325
  """Handle the store-dir command"""
312
326
  if not os.path.isdir(dir_path):
313
327
  print(f"Error: Directory {dir_path} not found")
@@ -331,7 +345,7 @@ def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
331
345
  for file_path, rel_path in all_files:
332
346
  try:
333
347
  print(f" Uploading: {rel_path}")
334
- file_result = client.upload_file(file_path, encrypt=encrypt)
348
+ file_result = await client.upload_file(file_path, encrypt=encrypt)
335
349
  individual_cids.append(
336
350
  {
337
351
  "path": rel_path,
@@ -351,7 +365,7 @@ def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
351
365
  print(f" Error uploading {rel_path}: {e}")
352
366
 
353
367
  # Now upload the entire directory
354
- result = client.upload_directory(dir_path, encrypt=encrypt)
368
+ result = await client.upload_directory(dir_path, encrypt=encrypt)
355
369
 
356
370
  ipfs_elapsed_time = time.time() - start_time
357
371
 
@@ -384,7 +398,7 @@ def handle_store_dir(client, dir_path, miner_ids, encrypt=None):
384
398
  file_inputs.append({"fileHash": item["cid"], "fileName": item["filename"]})
385
399
 
386
400
  # Store all files in a single batch request
387
- client.substrate_client.storage_request(file_inputs, miner_ids)
401
+ tx_hash = await client.substrate_client.storage_request(file_inputs, miner_ids)
388
402
 
389
403
  substrate_elapsed_time = time.time() - start_time
390
404
  print(
@@ -450,7 +464,7 @@ def handle_credits(client, account_address):
450
464
  return 0
451
465
 
452
466
 
453
- def handle_files(client, account_address, show_all_miners=False):
467
+ async def handle_files(client, account_address, show_all_miners=False):
454
468
  """
455
469
  Display files stored by a user in a nice format.
456
470
 
@@ -490,7 +504,9 @@ def handle_files(client, account_address, show_all_miners=False):
490
504
 
491
505
  # Get files for the account using the new profile-based method
492
506
  print(f"Retrieving files for account: {account_address}")
493
- files = client.substrate_client.get_user_files_from_profile(account_address)
507
+ files = await client.substrate_client.get_user_files_from_profile(
508
+ account_address
509
+ )
494
510
 
495
511
  # Check if any files were found
496
512
  if not files:
@@ -522,10 +538,10 @@ def handle_files(client, account_address, show_all_miners=False):
522
538
  if "size_formatted" in file and file["size_formatted"] is not None:
523
539
  size_formatted = file["size_formatted"]
524
540
  file_size = file.get("file_size", 0)
525
- if file_size is not None:
541
+ if file_size is not None and file_size > 0:
526
542
  print(f" File size: {file_size:,} bytes ({size_formatted})")
527
543
  else:
528
- print(f" File size: Unknown")
544
+ print(f" File size: {size_formatted}")
529
545
  else:
530
546
  print(f" File size: Unknown")
531
547
 
@@ -568,7 +584,9 @@ def handle_files(client, account_address, show_all_miners=False):
568
584
  return 0
569
585
 
570
586
 
571
- def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=False):
587
+ async def handle_ec_files(
588
+ client, account_address, show_all_miners=False, show_chunks=False
589
+ ):
572
590
  """Handle the ec-files command to show only erasure-coded files"""
573
591
  print("Looking for erasure-coded files...")
574
592
  try:
@@ -604,7 +622,9 @@ def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=
604
622
  return 1
605
623
 
606
624
  # First, get all user files using the profile method
607
- files = client.substrate_client.get_user_files_from_profile(account_address)
625
+ files = await client.substrate_client.get_user_files_from_profile(
626
+ account_address
627
+ )
608
628
 
609
629
  # Filter for metadata files (ending with .ec_metadata)
610
630
  ec_metadata_files = []
@@ -637,7 +657,7 @@ def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=
637
657
  # Fetch and parse the metadata to get original file info
638
658
  try:
639
659
  # Use the formatted CID, not the raw hex-encoded version
640
- metadata = client.ipfs_client.cat(formatted_cid)
660
+ metadata = await client.ipfs_client.cat(formatted_cid)
641
661
 
642
662
  # Check if we have text content
643
663
  if metadata.get("is_text", False):
@@ -772,7 +792,7 @@ def handle_ec_files(client, account_address, show_all_miners=False, show_chunks=
772
792
  return 0
773
793
 
774
794
 
775
- def handle_erasure_code(
795
+ async def handle_erasure_code(
776
796
  client, file_path, k, m, chunk_size, miner_ids, encrypt=None, verbose=True
777
797
  ):
778
798
  """Handle the erasure-code command"""
@@ -814,7 +834,7 @@ def handle_erasure_code(
814
834
  choice = input("> ").strip().lower()
815
835
 
816
836
  if choice in ("y", "yes"):
817
- return handle_erasure_code_directory(
837
+ return await handle_erasure_code_directory(
818
838
  client, file_path, k, m, chunk_size, miner_ids, encrypt, verbose
819
839
  )
820
840
  else:
@@ -879,7 +899,7 @@ def handle_erasure_code(
879
899
 
880
900
  try:
881
901
  # Use the store_erasure_coded_file method directly from HippiusClient
882
- result = client.store_erasure_coded_file(
902
+ result = await client.store_erasure_coded_file(
883
903
  file_path=file_path,
884
904
  k=k,
885
905
  m=m,
@@ -945,7 +965,7 @@ def handle_erasure_code(
945
965
  return 1
946
966
 
947
967
 
948
- def handle_erasure_code_directory(
968
+ async def handle_erasure_code_directory(
949
969
  client, dir_path, k, m, chunk_size, miner_ids, encrypt=None, verbose=True
950
970
  ):
951
971
  """Apply erasure coding to each file in a directory individually"""
@@ -1025,7 +1045,7 @@ def handle_erasure_code_directory(
1025
1045
 
1026
1046
  try:
1027
1047
  # Use the store_erasure_coded_file method directly from HippiusClient
1028
- result = client.store_erasure_coded_file(
1048
+ result = await client.store_erasure_coded_file(
1029
1049
  file_path=file_path,
1030
1050
  k=k,
1031
1051
  m=m,
@@ -1093,7 +1113,7 @@ def handle_erasure_code_directory(
1093
1113
  return 0 if failed == 0 else 1
1094
1114
 
1095
1115
 
1096
- def handle_reconstruct(client, metadata_cid, output_file, verbose=True):
1116
+ async def handle_reconstruct(client, metadata_cid, output_file, verbose=True):
1097
1117
  """Handle the reconstruct command for erasure-coded files"""
1098
1118
  # Check if zfec is installed
1099
1119
  try:
@@ -1112,7 +1132,7 @@ def handle_reconstruct(client, metadata_cid, output_file, verbose=True):
1112
1132
 
1113
1133
  try:
1114
1134
  # Use the reconstruct_from_erasure_code method
1115
- result = client.reconstruct_from_erasure_code(
1135
+ result = await client.reconstruct_from_erasure_code(
1116
1136
  metadata_cid=metadata_cid, output_file=output_file, verbose=verbose
1117
1137
  )
1118
1138
 
@@ -1500,6 +1520,261 @@ def handle_default_address_clear():
1500
1520
  return 0
1501
1521
 
1502
1522
 
1523
+ async def handle_pinning_status(
1524
+ client, account_address, verbose=False, show_contents=True
1525
+ ):
1526
+ """Handle the pinning-status command"""
1527
+ print("Checking file pinning status...")
1528
+ try:
1529
+ # Get the account address we're querying
1530
+ if account_address is None:
1531
+ # If no address provided, first try to get from keypair (if available)
1532
+ if (
1533
+ hasattr(client.substrate_client, "_keypair")
1534
+ and client.substrate_client._keypair is not None
1535
+ ):
1536
+ account_address = client.substrate_client._keypair.ss58_address
1537
+ else:
1538
+ # Try to get the default address
1539
+ default_address = get_default_address()
1540
+ if default_address:
1541
+ account_address = default_address
1542
+ else:
1543
+ has_default = get_default_address() is not None
1544
+ print(
1545
+ "Error: No account address provided, and client has no keypair."
1546
+ )
1547
+ if has_default:
1548
+ print(
1549
+ "Please provide an account address with '--account_address' or the default address may be invalid."
1550
+ )
1551
+ else:
1552
+ print(
1553
+ "Please provide an account address with '--account_address' or set a default with:"
1554
+ )
1555
+ print(" hippius address set-default <your_account_address>")
1556
+ return 1
1557
+
1558
+ storage_requests = client.substrate_client.get_pinning_status(account_address)
1559
+
1560
+ # Check if any storage requests were found
1561
+ if not storage_requests:
1562
+ print(f"No pinning requests found for account: {account_address}")
1563
+ return 0
1564
+
1565
+ print(
1566
+ f"\nFound {len(storage_requests)} pinning requests for account: {account_address}"
1567
+ )
1568
+ print("-" * 80)
1569
+
1570
+ # Format and display each storage request
1571
+ for i, request in enumerate(storage_requests, 1):
1572
+ try:
1573
+ print(f"Request {i}:")
1574
+
1575
+ # Display CID if available
1576
+ cid = None
1577
+ if "cid" in request:
1578
+ cid = request.get("cid", "Unknown")
1579
+ print(f" CID: {cid}")
1580
+
1581
+ # Display file name if available
1582
+ if "file_name" in request:
1583
+ file_name = request.get("file_name", "Unknown")
1584
+ print(f" File name: {file_name}")
1585
+ elif "raw_value" in request and "file_name" in request["raw_value"]:
1586
+ # Try to extract from raw value if it's available
1587
+ try:
1588
+ raw_value = request["raw_value"]
1589
+ if isinstance(raw_value, str) and "{" in raw_value:
1590
+ # It's a string representation of a dict, try to extract the file_name
1591
+ if "'file_name': " in raw_value:
1592
+ start_idx = raw_value.find("'file_name': '") + len(
1593
+ "'file_name': '"
1594
+ )
1595
+ end_idx = raw_value.find("'", start_idx)
1596
+ if start_idx > 0 and end_idx > start_idx:
1597
+ file_name = raw_value[start_idx:end_idx]
1598
+ print(f" File name: {file_name}")
1599
+ except Exception:
1600
+ pass
1601
+
1602
+ # Display total replicas if available
1603
+ if "total_replicas" in request:
1604
+ total_replicas = request.get("total_replicas", 0)
1605
+ print(f" Total replicas: {total_replicas}")
1606
+
1607
+ # Display owner if available
1608
+ if "owner" in request:
1609
+ owner = request.get("owner", "Unknown")
1610
+ print(f" Owner: {owner}")
1611
+
1612
+ # Display timestamps if available
1613
+ if "created_at" in request:
1614
+ created_at = request.get("created_at", 0)
1615
+ if created_at > 0:
1616
+ print(f" Created at block: {created_at}")
1617
+
1618
+ if "last_charged_at" in request:
1619
+ last_charged_at = request.get("last_charged_at", 0)
1620
+ if last_charged_at > 0:
1621
+ print(f" Last charged at block: {last_charged_at}")
1622
+
1623
+ # Display assignment status and progress info
1624
+ status_text = "Awaiting validator"
1625
+ if "is_assigned" in request:
1626
+ is_assigned = request.get("is_assigned", False)
1627
+ if is_assigned:
1628
+ status_text = "Assigned to miners"
1629
+
1630
+ # Enhanced status info
1631
+ if "miner_ids" in request and "total_replicas" in request:
1632
+ miner_ids = request.get("miner_ids", [])
1633
+ total_replicas = request.get("total_replicas", 0)
1634
+
1635
+ if len(miner_ids) > 0:
1636
+ if len(miner_ids) == total_replicas:
1637
+ status_text = "Fully pinned"
1638
+ else:
1639
+ status_text = "Partially pinned"
1640
+
1641
+ print(f" Status: {status_text}")
1642
+
1643
+ # Display validator if available
1644
+ if "selected_validator" in request:
1645
+ validator = request.get("selected_validator", "")
1646
+ if validator:
1647
+ print(f" Selected validator: {validator}")
1648
+
1649
+ # Display miners if available
1650
+ if "miner_ids" in request:
1651
+ miner_ids = request.get("miner_ids", [])
1652
+ if miner_ids:
1653
+ print(f" Assigned miners: {len(miner_ids)}")
1654
+ for miner in miner_ids[:3]: # Show first 3 miners
1655
+ print(f" - {miner}")
1656
+ if len(miner_ids) > 3:
1657
+ print(f" ... and {len(miner_ids) - 3} more")
1658
+ else:
1659
+ print(f" Assigned miners: None")
1660
+
1661
+ # Calculate pinning percentage if we have total_replicas
1662
+ if "total_replicas" in request and request["total_replicas"] > 0:
1663
+ total_replicas = request["total_replicas"]
1664
+ pinning_pct = (len(miner_ids) / total_replicas) * 100
1665
+ print(
1666
+ f" Pinning progress: {pinning_pct:.1f}% ({len(miner_ids)}/{total_replicas} miners)"
1667
+ )
1668
+
1669
+ # Display raw data for debugging
1670
+ if verbose:
1671
+ print(" Raw data:")
1672
+ if "raw_key" in request:
1673
+ print(f" Key: {request['raw_key']}")
1674
+ if "raw_value" in request:
1675
+ print(f" Value: {request['raw_value']}")
1676
+
1677
+ # Try to fetch the content and determine if it's a file list by inspecting its contents
1678
+ if show_contents and cid:
1679
+ try:
1680
+ print("\n Fetching contents from IPFS...")
1681
+ # Fetch the contents from IPFS
1682
+ file_data = await client.ipfs_client.cat(cid)
1683
+
1684
+ if file_data and file_data.get("is_text", False):
1685
+ try:
1686
+ # Try to parse as JSON
1687
+ content_json = json.loads(
1688
+ file_data.get("content", "{}")
1689
+ )
1690
+
1691
+ # Detect if this is a file list by checking if it's a list of file objects
1692
+ is_file_list = False
1693
+ if (
1694
+ isinstance(content_json, list)
1695
+ and len(content_json) > 0
1696
+ ):
1697
+ # Check if it looks like a file list
1698
+ sample_item = content_json[0]
1699
+ if isinstance(sample_item, dict) and (
1700
+ "cid" in sample_item
1701
+ or "fileHash" in sample_item
1702
+ or "filename" in sample_item
1703
+ or "fileName" in sample_item
1704
+ ):
1705
+ is_file_list = True
1706
+
1707
+ if is_file_list:
1708
+ # It's a file list - display the files
1709
+ print(
1710
+ f" Content is a file list with {len(content_json)} files:"
1711
+ )
1712
+ print(" " + "-" * 40)
1713
+ for j, file_info in enumerate(content_json, 1):
1714
+ filename = file_info.get(
1715
+ "filename"
1716
+ ) or file_info.get("fileName", "Unknown")
1717
+ file_cid = file_info.get(
1718
+ "cid"
1719
+ ) or file_info.get("fileHash", "Unknown")
1720
+ print(f" File {j}: {filename}")
1721
+ print(f" CID: {file_cid}")
1722
+
1723
+ # Show size if available
1724
+ if "size" in file_info:
1725
+ size = file_info["size"]
1726
+ size_formatted = (
1727
+ client.format_size(size)
1728
+ if hasattr(client, "format_size")
1729
+ else f"{size} bytes"
1730
+ )
1731
+ print(f" Size: {size_formatted}")
1732
+
1733
+ print(" " + "-" * 40)
1734
+ else:
1735
+ # Not a file list, show a compact summary
1736
+ content_type = type(content_json).__name__
1737
+ preview = str(content_json)
1738
+ if len(preview) > 100:
1739
+ preview = preview[:100] + "..."
1740
+ print(f" Content type: JSON {content_type}")
1741
+ print(f" Content preview: {preview}")
1742
+ except json.JSONDecodeError:
1743
+ # Not JSON, just show text preview
1744
+ content = file_data.get("content", "")
1745
+ preview = (
1746
+ content[:100] + "..."
1747
+ if len(content) > 100
1748
+ else content
1749
+ )
1750
+ print(f" Content type: Text")
1751
+ print(f" Content preview: {preview}")
1752
+ else:
1753
+ # Binary data
1754
+ content_size = len(file_data.get("content", b""))
1755
+ size_formatted = (
1756
+ client.format_size(content_size)
1757
+ if hasattr(client, "format_size")
1758
+ else f"{content_size} bytes"
1759
+ )
1760
+ print(f" Content type: Binary data")
1761
+ print(f" Content size: {size_formatted}")
1762
+ except Exception as e:
1763
+ print(f" Error fetching file list contents: {e}")
1764
+
1765
+ print("-" * 80)
1766
+ except Exception as e:
1767
+ print(f" Error displaying request {i}: {e}")
1768
+ print("-" * 80)
1769
+ continue
1770
+
1771
+ except Exception as e:
1772
+ print(f"Error retrieving pinning status: {e}")
1773
+ return 1
1774
+
1775
+ return 0
1776
+
1777
+
1503
1778
  def main():
1504
1779
  """Main CLI entry point for hippius command."""
1505
1780
  # Set up the argument parser
@@ -1532,6 +1807,9 @@ examples:
1532
1807
  # View all miners for stored files
1533
1808
  hippius files --all-miners
1534
1809
 
1810
+ # Check file pinning status
1811
+ hippius pinning-status
1812
+
1535
1813
  # Erasure code a file (Reed-Solomon)
1536
1814
  hippius erasure-code large_file.mp4 --k 3 --m 5
1537
1815
 
@@ -1678,6 +1956,32 @@ examples:
1678
1956
  )
1679
1957
  )
1680
1958
 
1959
+ # Pinning status command
1960
+ pinning_status_parser = subparsers.add_parser(
1961
+ "pinning-status", help="Check the status of file pinning requests"
1962
+ )
1963
+ pinning_status_parser.add_argument(
1964
+ "--account_address",
1965
+ help="Substrate account to check pinning status for (defaults to your keyfile account)",
1966
+ )
1967
+ pinning_status_parser.add_argument(
1968
+ "--verbose",
1969
+ "-v",
1970
+ action="store_true",
1971
+ help="Show detailed debug information",
1972
+ )
1973
+ pinning_status_parser.add_argument(
1974
+ "--show-contents",
1975
+ action="store_true",
1976
+ default=True,
1977
+ help="Show the contents of file lists (defaults to true)",
1978
+ )
1979
+ pinning_status_parser.add_argument(
1980
+ "--no-contents",
1981
+ action="store_true",
1982
+ help="Don't show the contents of file lists",
1983
+ )
1984
+
1681
1985
  # Erasure Coded Files command
1682
1986
  ec_files_parser = subparsers.add_parser(
1683
1987
  "ec-files", help="View erasure-coded files stored by you or another account"
@@ -1712,6 +2016,9 @@ examples:
1712
2016
  keygen_parser.add_argument(
1713
2017
  "--copy", action="store_true", help="Copy the generated key to the clipboard"
1714
2018
  )
2019
+ keygen_parser.add_argument(
2020
+ "--save", action="store_true", help="Save the key to the Hippius configuration"
2021
+ )
1715
2022
 
1716
2023
  # Erasure code command
1717
2024
  erasure_code_parser = subparsers.add_parser(
@@ -1897,16 +2204,6 @@ examples:
1897
2204
  parser.print_help()
1898
2205
  return 1
1899
2206
 
1900
- # Special case for keygen which doesn't need client initialization
1901
- if args.command == "keygen":
1902
- # Handle key generation separately
1903
- if args.copy:
1904
- return key_generation_cli()
1905
- else:
1906
- # Create a new argparse namespace with just the copy flag for compatibility
1907
- keygen_args = argparse.Namespace(copy=False)
1908
- return key_generation_cli()
1909
-
1910
2207
  try:
1911
2208
  # Parse miner IDs if provided
1912
2209
  miner_ids = None
@@ -1957,27 +2254,47 @@ examples:
1957
2254
  encryption_key=encryption_key,
1958
2255
  )
1959
2256
 
1960
- # Handle commands
2257
+ # Handle commands - separate async and sync handlers
2258
+ # Create a helper function to handle async handlers
2259
+ def run_async_handler(handler_func, *args, **kwargs):
2260
+ # Check if the handler is async
2261
+ if inspect.iscoroutinefunction(handler_func):
2262
+ # Run the async handler in the event loop
2263
+ return asyncio.run(handler_func(*args, **kwargs))
2264
+ else:
2265
+ # Run the handler directly
2266
+ return handler_func(*args, **kwargs)
2267
+
2268
+ # Handle commands with the helper function
1961
2269
  if args.command == "download":
1962
- return handle_download(client, args.cid, args.output_path, decrypt=decrypt)
2270
+ return run_async_handler(
2271
+ handle_download, client, args.cid, args.output_path, decrypt=decrypt
2272
+ )
1963
2273
 
1964
2274
  elif args.command == "exists":
1965
- return handle_exists(client, args.cid)
2275
+ return run_async_handler(handle_exists, client, args.cid)
1966
2276
 
1967
2277
  elif args.command == "cat":
1968
- return handle_cat(client, args.cid, args.max_size, decrypt=decrypt)
2278
+ return run_async_handler(
2279
+ handle_cat, client, args.cid, args.max_size, decrypt=decrypt
2280
+ )
1969
2281
 
1970
2282
  elif args.command == "store":
1971
- return handle_store(client, args.file_path, miner_ids, encrypt=encrypt)
2283
+ return run_async_handler(
2284
+ handle_store, client, args.file_path, miner_ids, encrypt=encrypt
2285
+ )
1972
2286
 
1973
2287
  elif args.command == "store-dir":
1974
- return handle_store_dir(client, args.dir_path, miner_ids, encrypt=encrypt)
2288
+ return run_async_handler(
2289
+ handle_store_dir, client, args.dir_path, miner_ids, encrypt=encrypt
2290
+ )
1975
2291
 
1976
2292
  elif args.command == "credits":
1977
2293
  return handle_credits(client, args.account_address)
1978
2294
 
1979
2295
  elif args.command == "files":
1980
- return handle_files(
2296
+ return run_async_handler(
2297
+ handle_files,
1981
2298
  client,
1982
2299
  args.account_address,
1983
2300
  show_all_miners=(
@@ -1985,8 +2302,21 @@ examples:
1985
2302
  ),
1986
2303
  )
1987
2304
 
2305
+ elif args.command == "pinning-status":
2306
+ show_contents = (
2307
+ not args.no_contents if hasattr(args, "no_contents") else True
2308
+ )
2309
+ return run_async_handler(
2310
+ handle_pinning_status,
2311
+ client,
2312
+ args.account_address,
2313
+ verbose=args.verbose,
2314
+ show_contents=show_contents,
2315
+ )
2316
+
1988
2317
  elif args.command == "ec-files":
1989
- return handle_ec_files(
2318
+ return run_async_handler(
2319
+ handle_ec_files,
1990
2320
  client,
1991
2321
  args.account_address,
1992
2322
  show_all_miners=(
@@ -1996,7 +2326,8 @@ examples:
1996
2326
  )
1997
2327
 
1998
2328
  elif args.command == "erasure-code":
1999
- return handle_erasure_code(
2329
+ return run_async_handler(
2330
+ handle_erasure_code,
2000
2331
  client,
2001
2332
  args.file_path,
2002
2333
  args.k,
@@ -2008,10 +2339,29 @@ examples:
2008
2339
  )
2009
2340
 
2010
2341
  elif args.command == "reconstruct":
2011
- return handle_reconstruct(
2012
- client, args.metadata_cid, args.output_file, verbose=args.verbose
2342
+ return run_async_handler(
2343
+ handle_reconstruct,
2344
+ client,
2345
+ args.metadata_cid,
2346
+ args.output_file,
2347
+ verbose=args.verbose,
2013
2348
  )
2014
2349
 
2350
+ elif args.command == "keygen":
2351
+ # Generate and save an encryption key
2352
+ client = HippiusClient()
2353
+ encryption_key = client.generate_encryption_key()
2354
+ print(f"Generated encryption key: {encryption_key}")
2355
+
2356
+ # Save to config if requested
2357
+ if hasattr(args, "save") and args.save:
2358
+ print("Saving encryption key to configuration...")
2359
+ handle_config_set("encryption", "encryption_key", encryption_key)
2360
+ print(
2361
+ "Encryption key saved. Files will not be automatically encrypted unless you set encryption.encrypt_by_default to true"
2362
+ )
2363
+ return 0
2364
+
2015
2365
  elif args.command == "config":
2016
2366
  if args.config_action == "get":
2017
2367
  return handle_config_get(args.section, args.key)