akashcli 3.6.0__tar.gz → 4.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 3.6.0
3
+ Version: 4.2.0
4
4
  Summary: Enterprise Developer SDK and CLI for Akash Vault
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -7,7 +7,7 @@ import webbrowser
7
7
  import httpx
8
8
  import urllib.parse
9
9
  import questionary
10
- from typing import Optional
10
+ from typing import Optional, List
11
11
  from pathlib import Path
12
12
  from rich.console import Console
13
13
  from rich.table import Table
@@ -194,21 +194,32 @@ def files(json_output: bool = typer.Option(False, "--json", help="Output in raw
194
194
  @app.command()
195
195
  def download(
196
196
  code: str,
197
- output: Optional[Path] = typer.Option(None, "--output", "-o", help="Custom save path")
197
+ output: Optional[Path] = typer.Option(None, "--output", "-o")
198
198
  ):
199
- """📥 Download a file from the vault to your local machine."""
199
+ """📥 Download a file from the vault to your current folder."""
200
200
  try:
201
201
  client = AkashClient()
202
- # Attach the console to the client for the progress bar
203
- client.console = console
204
202
 
205
- from .downloader import download_file
206
- path = download_file(client, code, output)
203
+ # 1. Get the secure link
204
+ with console.status("[bold cyan]Requesting link..."):
205
+ resp = client.generate_secure_link(code)
206
+ if not resp or 'stream_url' not in resp:
207
+ raise Exception("Failed to generate download link.")
208
+
209
+ # 2. Start the download
210
+ from .downloader import smart_download
211
+ # Convert Path to string for the downloader
212
+ out_str = str(output) if output else None
213
+ saved_path = smart_download(resp['stream_url'], out_str)
207
214
 
208
- console.print(f"[bold green]✓ Download Complete![/bold green]")
209
- console.print(f"📍 Saved to: [cyan]{path}[/cyan]")
215
+ if saved_path:
216
+ console.print(f"[bold green]✓ Download Complete![/bold green]")
217
+ console.print(f"📍 Saved as: [bold cyan]{saved_path}[/bold cyan]")
218
+ else:
219
+ console.print("[red]❌ Download failed (Empty stream).[/red]")
220
+
210
221
  except Exception as e:
211
- console.print(f"[bold red]✗ Download Failed:[/bold red] {e}")
222
+ console.print(f"[bold red]✗ Error:[/bold red] {e}")
212
223
 
213
224
  @app.command()
214
225
  def delete(code: str):
@@ -324,30 +335,22 @@ def share(code: Optional[str] = typer.Argument(None)):
324
335
 
325
336
  @app.command()
326
337
  def get(otp: str):
327
- """✨ Download a file using a 6-digit temporary code."""
328
- url = os.getenv("AKASH_API_URL", "https://jstore.2bd.net").rstrip("/")
338
+ """✨ Download a file using a 6-digit OTP code."""
339
+ config = ConfigManager.load()
340
+ base_url = os.getenv("AKASH_API_URL") or config.get("api_url", "https://jstore.2bd.net")
329
341
  try:
330
342
  with console.status("[bold cyan]Redeeming code..."):
331
- resp = httpx.get(f"{url}/api/otp/redeem/{otp}")
332
- if resp.status_code != 200:
333
- console.print("[red]❌ Invalid or expired code.[/red]")
334
- return
335
-
336
- data = resp.json()
337
- stream_url = data['stream_url']
338
- # We don't have the original filename easily, so we probe the headers
339
- head = httpx.head(stream_url)
340
- cd = head.headers.get("Content-Disposition", "")
341
- filename = cd.split("filename*=UTF-8''")[-1] if "''" in cd else f"shared_{otp}.bin"
342
- filename = urllib.parse.unquote(filename)
343
-
344
- # Re-use your high-performance downloader logic
345
- from .downloader import download_from_url
346
- download_from_url(stream_url, filename)
343
+ resp = httpx.get(f"{base_url}/api/otp/redeem/{otp}")
344
+ resp.raise_for_status()
345
+ stream_url = resp.json()['stream_url']
346
+
347
+ from .downloader import smart_download
348
+ saved_path = smart_download(stream_url)
347
349
 
348
- console.print(f"[bold green]✓ Successfully retrieved: {filename}[/bold green]")
350
+ if saved_path:
351
+ console.print(f"[bold green]✓ Successfully retrieved:[/bold green] [cyan]{saved_path}[/cyan]")
349
352
  except Exception as e:
350
- console.print(f"[red]Error:[/red] {e}")
353
+ console.print(f"[bold red]✗ Failed:[/bold red] {e}")
351
354
 
352
355
  @app.command()
353
356
  def bulk(path: Path):
@@ -0,0 +1,47 @@
1
+ import httpx
2
+ import os
3
+ import re
4
+ import urllib.parse
5
+ from pathlib import Path
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ from rich.progress import Progress, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn
9
+
10
+ def smart_download(url: str, custom_filename: Optional[str] = None):
11
+ """Unified downloader that detects server errors properly."""
12
+ # We use a standard get first to check the response
13
+ with httpx.stream("GET", url, follow_redirects=True, timeout=None) as response:
14
+ # 🌟 THE FIX: If not 200/206, read the error message 🌟
15
+ if response.status_code not in [200, 206]:
16
+ # Load the first few bytes to see the error
17
+ error_text = response.read().decode('utf-8', errors='ignore')
18
+ print(f"\n[red]❌ Server Error ({response.status_code}):[/red] {error_text[:200]}")
19
+ return None
20
+
21
+ # Robust Filename Extraction
22
+ raw_name = response.headers.get("x-vault-filename")
23
+ if raw_name:
24
+ filename = urllib.parse.unquote(raw_name)
25
+ else:
26
+ cd = response.headers.get("Content-Disposition", "")
27
+ match = re.search(r"filename\*=UTF-8''([^;]+)", cd, re.IGNORECASE)
28
+ filename = urllib.parse.unquote(match.group(1)) if match else f"file_{os.urandom(2).hex()}.bin"
29
+
30
+ final_name = custom_filename or filename
31
+ total_size = int(response.headers.get("x-vault-size") or response.headers.get("Content-Length", 0))
32
+
33
+ # Stream the actual data
34
+ with Progress(
35
+ TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
36
+ BarColumn(bar_width=None),
37
+ "[progress.percentage]{task.percentage:>3.1f}%",
38
+ DownloadColumn(),
39
+ console=Console()
40
+ ) as progress:
41
+ task = progress.add_task("download", total=total_size, filename=final_name)
42
+ with open(final_name, "wb") as f:
43
+ for chunk in response.iter_bytes():
44
+ f.write(chunk)
45
+ progress.update(task, advance=len(chunk))
46
+
47
+ return final_name
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: akashcli
3
- Version: 3.6.0
3
+ Version: 4.2.0
4
4
  Summary: Enterprise Developer SDK and CLI for Akash Vault
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "akashcli"
3
- version = "3.6.0"
3
+ version = "4.2.0"
4
4
  description = "Enterprise Developer SDK and CLI for Akash Vault"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,28 +0,0 @@
1
- # akashcli/downloader.py
2
- import httpx
3
- import os
4
- from pathlib import Path
5
- from rich.console import Console # 🌟 ADD THIS
6
- from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, DownloadColumn
7
-
8
- def download_from_url(url: str, filename: str):
9
- """Standalone downloader for OTP/Guest links."""
10
- # 🌟 FIX: Define the path
11
- final_path = Path(os.getcwd()) / filename
12
-
13
- with httpx.stream("GET", url, follow_redirects=True) as response:
14
- total = int(response.headers.get("Content-Length", 0))
15
-
16
- with Progress(
17
- TextColumn("[bold blue]{task.fields[filename]}", justify="right"),
18
- BarColumn(bar_width=None),
19
- "[progress.percentage]{task.percentage:>3.1f}%",
20
- DownloadColumn(),
21
- console=Console()
22
- ) as progress:
23
- task = progress.add_task("download", total=total, filename=filename)
24
- with open(final_path, "wb") as f:
25
- for chunk in response.iter_bytes():
26
- f.write(chunk)
27
- progress.update(task, advance=len(chunk))
28
- return final_path # 🌟 Now defined
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes