akashcli 3.0.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.
- akashcli-3.0.0/PKG-INFO +137 -0
- akashcli-3.0.0/README.md +126 -0
- akashcli-3.0.0/akashcli/__init__.py +6 -0
- akashcli-3.0.0/akashcli/auth.py +51 -0
- akashcli-3.0.0/akashcli/cli.py +184 -0
- akashcli-3.0.0/akashcli/client.py +74 -0
- akashcli-3.0.0/akashcli/config.py +52 -0
- akashcli-3.0.0/akashcli/crypto.py +42 -0
- akashcli-3.0.0/akashcli/exceptions.py +27 -0
- akashcli-3.0.0/akashcli/models.py +26 -0
- akashcli-3.0.0/akashcli/uploader.py +123 -0
- akashcli-3.0.0/akashcli/utils.py +9 -0
- akashcli-3.0.0/akashcli.egg-info/PKG-INFO +137 -0
- akashcli-3.0.0/akashcli.egg-info/SOURCES.txt +18 -0
- akashcli-3.0.0/akashcli.egg-info/dependency_links.txt +1 -0
- akashcli-3.0.0/akashcli.egg-info/entry_points.txt +2 -0
- akashcli-3.0.0/akashcli.egg-info/requires.txt +4 -0
- akashcli-3.0.0/akashcli.egg-info/top_level.txt +1 -0
- akashcli-3.0.0/pyproject.toml +19 -0
- akashcli-3.0.0/setup.cfg +4 -0
akashcli-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akashcli
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Enterprise Developer SDK and CLI for Akash Vault
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx
|
|
8
|
+
Requires-Dist: typer[all]
|
|
9
|
+
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: cryptography
|
|
11
|
+
|
|
12
|
+
To get started with **AkashCLI**, your users should follow these steps. You can provide this guide as a `README.md` or a "Getting Started" message in your bot.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# 🚀 Akash Vault: Getting Started Guide
|
|
17
|
+
|
|
18
|
+
**AkashCLI** is the professional command-line interface and SDK for interacting with your E2E encrypted vault. Follow these steps to install and secure your first files.
|
|
19
|
+
|
|
20
|
+
## 1. Prerequisites
|
|
21
|
+
Ensure you have **Python 3.10+** and `pip` installed on your machine (Linux, Termux, Mac, or Windows).
|
|
22
|
+
|
|
23
|
+
## 2. Installation
|
|
24
|
+
Since this is a custom package, users can install it directly from your source code or repository:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone the repository
|
|
28
|
+
git clone https://github.com/juniorsir/akashcli
|
|
29
|
+
cd akashcli
|
|
30
|
+
|
|
31
|
+
# Install in editable mode
|
|
32
|
+
pip install . --break-system-packages
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. First-Time Setup (Authentication)
|
|
38
|
+
Before using the vault, you must link your device using an API Key generated from the [Akash Telegram Bot](https://t.me/EveryDL_bot).
|
|
39
|
+
|
|
40
|
+
1. Open the Bot and go to **👤 My Account** -> **🔑 Generate API Key**.
|
|
41
|
+
2. In your terminal, run:
|
|
42
|
+
```bash
|
|
43
|
+
akash login
|
|
44
|
+
```
|
|
45
|
+
3. Enter your API Key when prompted. Your key will be **encrypted and bound to your hardware** automatically.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 4. Daily Workflow Commands
|
|
50
|
+
|
|
51
|
+
### 📤 Uploading Content
|
|
52
|
+
**Upload a file:**
|
|
53
|
+
```bash
|
|
54
|
+
akash upload movie.mp4
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Upload a secret text snippet:**
|
|
58
|
+
```bash
|
|
59
|
+
akash upload --text "My secret server password is 1234" --name "passwords.txt"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Upload with security flags:**
|
|
63
|
+
```bash
|
|
64
|
+
# Delete automatically after the first person views it
|
|
65
|
+
akash upload --burn secret_doc.pdf
|
|
66
|
+
|
|
67
|
+
# Set a 7-day expiry
|
|
68
|
+
akash upload --expiry 7 data_backup.zip
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 📦 Managing the Vault
|
|
72
|
+
**List your most recent files:**
|
|
73
|
+
```bash
|
|
74
|
+
akash files
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Get file details or secure links:**
|
|
78
|
+
```bash
|
|
79
|
+
akash stream FILE_CODE
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Delete a file permanently:**
|
|
83
|
+
```bash
|
|
84
|
+
akash delete FILE_CODE
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 5. Professional Features
|
|
90
|
+
|
|
91
|
+
### 🚀 Bulk Vaulting
|
|
92
|
+
Vault an entire folder of documents or images at once:
|
|
93
|
+
```bash
|
|
94
|
+
akash bulk ./my_documents_folder
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 🎬 Native Streaming
|
|
98
|
+
Stream a vaulted video directly into **VLC Media Player** without downloading:
|
|
99
|
+
```bash
|
|
100
|
+
akash stream FILE_CODE --vlc
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 📊 System Health
|
|
104
|
+
Check your remaining quota and vault status:
|
|
105
|
+
```bash
|
|
106
|
+
akash admin
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 6. Using the SDK (For Developers)
|
|
112
|
+
If you are building your own Python scripts, you can import the library directly:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from akashcli import AkashClient
|
|
116
|
+
|
|
117
|
+
# Initialize client (uses your encrypted local config)
|
|
118
|
+
client = AkashClient()
|
|
119
|
+
|
|
120
|
+
# Vault a log file
|
|
121
|
+
result = client.upload_file("system.log", expiry=30)
|
|
122
|
+
print(f"File secured! Access it at: {result.embed_url}")
|
|
123
|
+
|
|
124
|
+
# List all your files programmatically
|
|
125
|
+
for file in client.list_files():
|
|
126
|
+
print(f"{file.name} -> {file.code}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 💡 Troubleshooting
|
|
132
|
+
* **Access Denied?** Ensure you have set your API Key domains to `*` in the Telegram Bot under "Edit Domains."
|
|
133
|
+
* **VLC not opening?** Ensure the `vlc` command is available in your system's PATH.
|
|
134
|
+
* **Forgot your key?** Run `akash login` again to overwrite the old session.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
**🛡️ Powered by Akash Enterprise Core.**
|
akashcli-3.0.0/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
To get started with **AkashCLI**, your users should follow these steps. You can provide this guide as a `README.md` or a "Getting Started" message in your bot.
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 🚀 Akash Vault: Getting Started Guide
|
|
6
|
+
|
|
7
|
+
**AkashCLI** is the professional command-line interface and SDK for interacting with your E2E encrypted vault. Follow these steps to install and secure your first files.
|
|
8
|
+
|
|
9
|
+
## 1. Prerequisites
|
|
10
|
+
Ensure you have **Python 3.10+** and `pip` installed on your machine (Linux, Termux, Mac, or Windows).
|
|
11
|
+
|
|
12
|
+
## 2. Installation
|
|
13
|
+
Since this is a custom package, users can install it directly from your source code or repository:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Clone the repository
|
|
17
|
+
git clone https://github.com/juniorsir/akashcli
|
|
18
|
+
cd akashcli
|
|
19
|
+
|
|
20
|
+
# Install in editable mode
|
|
21
|
+
pip install . --break-system-packages
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 3. First-Time Setup (Authentication)
|
|
27
|
+
Before using the vault, you must link your device using an API Key generated from the [Akash Telegram Bot](https://t.me/EveryDL_bot).
|
|
28
|
+
|
|
29
|
+
1. Open the Bot and go to **👤 My Account** -> **🔑 Generate API Key**.
|
|
30
|
+
2. In your terminal, run:
|
|
31
|
+
```bash
|
|
32
|
+
akash login
|
|
33
|
+
```
|
|
34
|
+
3. Enter your API Key when prompted. Your key will be **encrypted and bound to your hardware** automatically.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 4. Daily Workflow Commands
|
|
39
|
+
|
|
40
|
+
### 📤 Uploading Content
|
|
41
|
+
**Upload a file:**
|
|
42
|
+
```bash
|
|
43
|
+
akash upload movie.mp4
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Upload a secret text snippet:**
|
|
47
|
+
```bash
|
|
48
|
+
akash upload --text "My secret server password is 1234" --name "passwords.txt"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Upload with security flags:**
|
|
52
|
+
```bash
|
|
53
|
+
# Delete automatically after the first person views it
|
|
54
|
+
akash upload --burn secret_doc.pdf
|
|
55
|
+
|
|
56
|
+
# Set a 7-day expiry
|
|
57
|
+
akash upload --expiry 7 data_backup.zip
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 📦 Managing the Vault
|
|
61
|
+
**List your most recent files:**
|
|
62
|
+
```bash
|
|
63
|
+
akash files
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Get file details or secure links:**
|
|
67
|
+
```bash
|
|
68
|
+
akash stream FILE_CODE
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Delete a file permanently:**
|
|
72
|
+
```bash
|
|
73
|
+
akash delete FILE_CODE
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 5. Professional Features
|
|
79
|
+
|
|
80
|
+
### 🚀 Bulk Vaulting
|
|
81
|
+
Vault an entire folder of documents or images at once:
|
|
82
|
+
```bash
|
|
83
|
+
akash bulk ./my_documents_folder
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 🎬 Native Streaming
|
|
87
|
+
Stream a vaulted video directly into **VLC Media Player** without downloading:
|
|
88
|
+
```bash
|
|
89
|
+
akash stream FILE_CODE --vlc
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 📊 System Health
|
|
93
|
+
Check your remaining quota and vault status:
|
|
94
|
+
```bash
|
|
95
|
+
akash admin
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 6. Using the SDK (For Developers)
|
|
101
|
+
If you are building your own Python scripts, you can import the library directly:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from akashcli import AkashClient
|
|
105
|
+
|
|
106
|
+
# Initialize client (uses your encrypted local config)
|
|
107
|
+
client = AkashClient()
|
|
108
|
+
|
|
109
|
+
# Vault a log file
|
|
110
|
+
result = client.upload_file("system.log", expiry=30)
|
|
111
|
+
print(f"File secured! Access it at: {result.embed_url}")
|
|
112
|
+
|
|
113
|
+
# List all your files programmatically
|
|
114
|
+
for file in client.list_files():
|
|
115
|
+
print(f"{file.name} -> {file.code}")
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 💡 Troubleshooting
|
|
121
|
+
* **Access Denied?** Ensure you have set your API Key domains to `*` in the Telegram Bot under "Edit Domains."
|
|
122
|
+
* **VLC not opening?** Ensure the `vlc` command is available in your system's PATH.
|
|
123
|
+
* **Forgot your key?** Run `akash login` again to overwrite the old session.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
**🛡️ Powered by Akash Enterprise Core.**
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import json
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .models import UserInfo
|
|
5
|
+
from .exceptions import AuthenticationError, NetworkError
|
|
6
|
+
from .config import ConfigManager
|
|
7
|
+
from .crypto import SecureVault
|
|
8
|
+
|
|
9
|
+
class AuthManager:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def verify_remote_key(api_key: str, api_url: str) -> UserInfo:
|
|
12
|
+
"""Calls the /api/verify endpoint to check key status."""
|
|
13
|
+
try:
|
|
14
|
+
with httpx.Client(base_url=api_url, timeout=10.0) as client:
|
|
15
|
+
response = client.get(
|
|
16
|
+
"/api/verify",
|
|
17
|
+
headers={"x-api-key": api_key, "Origin": "https://akash.cli"}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
if response.status_code == 403:
|
|
21
|
+
raise AuthenticationError("API Key is invalid or locked.")
|
|
22
|
+
|
|
23
|
+
response.raise_for_status()
|
|
24
|
+
data = response.json()
|
|
25
|
+
|
|
26
|
+
return UserInfo(
|
|
27
|
+
owner=data["owner"],
|
|
28
|
+
quota_used=data["quota_used"],
|
|
29
|
+
quota_limit=data["quota_limit"],
|
|
30
|
+
allowed_domains=data.get("allowed_domains", "*")
|
|
31
|
+
)
|
|
32
|
+
except httpx.RequestError as e:
|
|
33
|
+
raise NetworkError(f"Could not connect to Akash Vault: {e}")
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def perform_login(api_key: str, api_url: str) -> UserInfo:
|
|
37
|
+
"""Verifies the key and persists it to the encrypted config."""
|
|
38
|
+
user_info = AuthManager.verify_remote_key(api_key, api_url)
|
|
39
|
+
|
|
40
|
+
# Prepare data for local encrypted storage
|
|
41
|
+
config_data = {
|
|
42
|
+
"api_key": api_key,
|
|
43
|
+
"api_url": api_url,
|
|
44
|
+
"owner": user_info.owner
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Encrypt and save
|
|
48
|
+
encrypted_blob = SecureVault.encrypt(json.dumps(config_data))
|
|
49
|
+
ConfigManager.save(encrypted_blob)
|
|
50
|
+
|
|
51
|
+
return user_info
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import webbrowser
|
|
6
|
+
import httpx
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, DownloadColumn, TransferSpeedColumn
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
|
|
14
|
+
# Internal Imports
|
|
15
|
+
from .client import AkashClient
|
|
16
|
+
from .auth import AuthManager
|
|
17
|
+
from .config import ConfigManager
|
|
18
|
+
from .exceptions import AkashError
|
|
19
|
+
from .uploader import VaultUploader
|
|
20
|
+
|
|
21
|
+
# 🌟 Removed the 'gh' overrides and panels
|
|
22
|
+
app = typer.Typer(help="🛡️ Akash Vault Enterprise CLI", add_completion=True)
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
@app.command()
|
|
26
|
+
def login(
|
|
27
|
+
url: str = typer.Option("https://jstore.2bd.net", "--url", help="Custom Vault URL"),
|
|
28
|
+
key: str = typer.Option(..., prompt="🔑 Enter API Key", hide_input=True)
|
|
29
|
+
):
|
|
30
|
+
"""🔑 Link your device to the vault."""
|
|
31
|
+
try:
|
|
32
|
+
with console.status("[bold blue]Verifying identity..."):
|
|
33
|
+
user = AuthManager.perform_login(key, url)
|
|
34
|
+
|
|
35
|
+
console.print(f"[green]✓ Login Successful![/green] Owner: {user.owner}")
|
|
36
|
+
except Exception as e:
|
|
37
|
+
console.print(f"[bold red]✗ Authentication Failed:[/bold red] {e}")
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def whoami():
|
|
41
|
+
"""👤 Check current login and quota status."""
|
|
42
|
+
try:
|
|
43
|
+
client = AkashClient()
|
|
44
|
+
user = client.whoami()
|
|
45
|
+
console.print(f"Logged in as: [bold cyan]{user.owner}[/bold cyan]")
|
|
46
|
+
console.print(f"Storage Quota: [bold yellow]{user.quota_used}/{user.quota_limit}[/bold yellow]")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
49
|
+
|
|
50
|
+
@app.command()
|
|
51
|
+
def upload(
|
|
52
|
+
path: Optional[Path] = typer.Argument(None, help="Path to file"),
|
|
53
|
+
text: Optional[str] = typer.Option(None, "--text", "-t", help="Upload raw text content"),
|
|
54
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Custom filename"),
|
|
55
|
+
burn: bool = typer.Option(False, "--burn", help="Self-destruct after one view"),
|
|
56
|
+
expiry: int = typer.Option(0, "--expiry", "-e", help="Expiry in days (0 for forever)"),
|
|
57
|
+
password: Optional[str] = typer.Option(None, "--pass", "-p", help="Password protect file")
|
|
58
|
+
):
|
|
59
|
+
"""📤 Vault a single file or text snippet."""
|
|
60
|
+
try:
|
|
61
|
+
client = AkashClient()
|
|
62
|
+
|
|
63
|
+
with Progress(
|
|
64
|
+
SpinnerColumn(),
|
|
65
|
+
TextColumn("[progress.description]{task.description}"),
|
|
66
|
+
BarColumn(),
|
|
67
|
+
console=console,
|
|
68
|
+
transient=True
|
|
69
|
+
) as progress:
|
|
70
|
+
task = progress.add_task(description="[cyan]Processing...", total=100)
|
|
71
|
+
def update_bar(current, total):
|
|
72
|
+
progress.update(task, completed=(current/total)*100)
|
|
73
|
+
|
|
74
|
+
if text:
|
|
75
|
+
res = client.upload_text(text, filename=name, burn=burn, expiry=expiry, password=password)
|
|
76
|
+
else:
|
|
77
|
+
if not path:
|
|
78
|
+
console.print("[red]Error:[/red] Provide a file path or use --text")
|
|
79
|
+
return
|
|
80
|
+
res = client.upload_file(str(path), burn=burn, expiry=expiry, password=password, progress_callback=update_bar)
|
|
81
|
+
|
|
82
|
+
console.print(f"[bold green]✓ Vaulted![/bold green] Code: [yellow]{res.file_code}[/yellow]")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
console.print(f"[bold red]✗ Upload Failed:[/bold red] {e}")
|
|
85
|
+
|
|
86
|
+
@app.command()
|
|
87
|
+
def files(json_output: bool = typer.Option(False, "--json", help="Output in raw JSON")):
|
|
88
|
+
"""📦 List and search files in your vault."""
|
|
89
|
+
try:
|
|
90
|
+
client = AkashClient()
|
|
91
|
+
file_list = client.list_files()
|
|
92
|
+
|
|
93
|
+
if json_output:
|
|
94
|
+
console.print_json(data=[f.__dict__ for f in file_list])
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
table = Table(title="📦 Encrypted Vault")
|
|
98
|
+
table.add_column("Filename", style="cyan")
|
|
99
|
+
table.add_column("Code", style="yellow")
|
|
100
|
+
table.add_column("Size", justify="right")
|
|
101
|
+
|
|
102
|
+
for f in file_list:
|
|
103
|
+
size_mb = f"{f.size / (1024*1024):.2f} MB"
|
|
104
|
+
table.add_row(f.name, f.code, size_mb)
|
|
105
|
+
|
|
106
|
+
console.print(table)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
109
|
+
|
|
110
|
+
@app.command()
|
|
111
|
+
def stream(code: str, vlc: bool = typer.Option(False, "--vlc", help="Open directly in VLC")):
|
|
112
|
+
"""🎬 Get a secure streaming link."""
|
|
113
|
+
client = AkashClient()
|
|
114
|
+
try:
|
|
115
|
+
with httpx.Client(base_url=client.base_url) as c:
|
|
116
|
+
resp = c.get(f"/api/stream/generate/{code}", headers=client._get_headers())
|
|
117
|
+
resp.raise_for_status()
|
|
118
|
+
url = resp.json()['stream_url']
|
|
119
|
+
|
|
120
|
+
if vlc:
|
|
121
|
+
console.print(f"🎬 Launching VLC for: {code}")
|
|
122
|
+
subprocess.Popen(['vlc', url], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
123
|
+
else:
|
|
124
|
+
console.print(f"🎬 Secure URL: {url}")
|
|
125
|
+
except Exception as e:
|
|
126
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
127
|
+
|
|
128
|
+
@app.command()
|
|
129
|
+
def delete(code: str):
|
|
130
|
+
"""🗑️ Permanently remove a file."""
|
|
131
|
+
if typer.confirm(f"Are you sure you want to delete {code}?"):
|
|
132
|
+
try:
|
|
133
|
+
client = AkashClient()
|
|
134
|
+
client.delete_file(code)
|
|
135
|
+
console.print(f"[green]✓ Deleted {code}[/green]")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
138
|
+
|
|
139
|
+
@app.command()
|
|
140
|
+
def manage(
|
|
141
|
+
code: str,
|
|
142
|
+
name: Optional[str] = typer.Option(None, "--name"),
|
|
143
|
+
burn: Optional[bool] = typer.Option(None, "--burn/--no-burn"),
|
|
144
|
+
expiry: Optional[int] = typer.Option(None, "--expiry")
|
|
145
|
+
):
|
|
146
|
+
"""⚙️ Modify settings for an existing file."""
|
|
147
|
+
try:
|
|
148
|
+
client = AkashClient()
|
|
149
|
+
updates = {}
|
|
150
|
+
if name: updates['filename'] = name
|
|
151
|
+
if burn is not None: updates['burn_on_read'] = 1 if burn else 0
|
|
152
|
+
if expiry is not None: updates['expiry_days'] = expiry
|
|
153
|
+
client.update_file(code, **updates)
|
|
154
|
+
console.print(f"[green]✓ Updated {code}[/green]")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
157
|
+
|
|
158
|
+
@app.command()
|
|
159
|
+
def bulk(path: Path):
|
|
160
|
+
"""🚀 Recursive folder vaulting."""
|
|
161
|
+
try:
|
|
162
|
+
client = AkashClient()
|
|
163
|
+
uploader = VaultUploader(client)
|
|
164
|
+
files = [p for p in path.glob("**/*") if p.is_file() and not p.name.startswith('.')]
|
|
165
|
+
for file in files:
|
|
166
|
+
console.print(f"Vaulting: {file.name}")
|
|
167
|
+
uploader.upload(file_path=str(file))
|
|
168
|
+
console.print("[green]✓ Batch complete.[/green]")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
171
|
+
|
|
172
|
+
@app.command()
|
|
173
|
+
def docs():
|
|
174
|
+
"""📖 View the integration guide."""
|
|
175
|
+
webbrowser.open("https://github.com/juniorsir/akashcli")
|
|
176
|
+
|
|
177
|
+
@app.command()
|
|
178
|
+
def logout():
|
|
179
|
+
"""🚪 Clear local credentials."""
|
|
180
|
+
ConfigManager.save(b"")
|
|
181
|
+
console.print("[yellow]Logged out.[/yellow]")
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
app()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import time
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from .models import UploadResult, FileInfo, UserInfo
|
|
5
|
+
from .exceptions import *
|
|
6
|
+
from .config import ConfigManager
|
|
7
|
+
|
|
8
|
+
class AkashClient:
|
|
9
|
+
def __init__(self, api_key: Optional[str] = None, api_url: Optional[str] = None):
|
|
10
|
+
config = ConfigManager.load()
|
|
11
|
+
self.api_key = api_key or config.get("api_key")
|
|
12
|
+
self.base_url = (api_url or config.get("api_url", "https://jstore.2bd.net")).rstrip("/")
|
|
13
|
+
|
|
14
|
+
if not self.api_key:
|
|
15
|
+
raise AuthenticationError("No API Key found. Run 'akash login'")
|
|
16
|
+
|
|
17
|
+
def _get_headers(self, custom: dict = None):
|
|
18
|
+
h = {"x-api-key": self.api_key, "Origin": "https://akash.cli"}
|
|
19
|
+
if custom: h.update(custom)
|
|
20
|
+
return h
|
|
21
|
+
|
|
22
|
+
def whoami(self) -> UserInfo:
|
|
23
|
+
with httpx.Client(base_url=self.base_url) as client:
|
|
24
|
+
resp = client.get("/api/me", headers=self._get_headers())
|
|
25
|
+
if resp.status_code != 200: raise AuthenticationError("Invalid Key")
|
|
26
|
+
d = resp.json()
|
|
27
|
+
return UserInfo(d['owner'], d['quota']['used'], d['quota']['limit'], d['allowed_domains'])
|
|
28
|
+
|
|
29
|
+
def upload_file(self, path: str, burn: bool = False, expiry: int = 0) -> UploadResult:
|
|
30
|
+
files = {"file": open(path, "rb")}
|
|
31
|
+
headers = {
|
|
32
|
+
"x-vault-burn": "1" if burn else "0",
|
|
33
|
+
"x-vault-expiry": str(expiry)
|
|
34
|
+
}
|
|
35
|
+
with httpx.Client(base_url=self.base_url, timeout=None) as client:
|
|
36
|
+
resp = client.post("/api/upload", headers=self._get_headers(headers), files=files)
|
|
37
|
+
resp.raise_for_status()
|
|
38
|
+
d = resp.json()
|
|
39
|
+
return UploadResult(
|
|
40
|
+
d['status'], d['file_code'], d['links']['embed'],
|
|
41
|
+
d['links']['direct'], d['input_type'], d.get('dna', {})
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def list_files(self) -> List[FileInfo]:
|
|
45
|
+
with httpx.Client(base_url=self.base_url) as client:
|
|
46
|
+
resp = client.get("/api/files", headers=self._get_headers())
|
|
47
|
+
return [FileInfo(f['name'], f['code'], f['size'], f['uploaded_at']) for f in resp.json()]
|
|
48
|
+
|
|
49
|
+
def delete_file(self, code: str):
|
|
50
|
+
with httpx.Client(base_url=self.base_url) as client:
|
|
51
|
+
client.delete(f"/api/files/{code}", headers=self._get_headers())
|
|
52
|
+
|
|
53
|
+
def upload_text(self, content: str, filename: str = None, burn: bool = False, expiry: int = 0, password: str = None):
|
|
54
|
+
from .uploader import VaultUploader
|
|
55
|
+
uploader = VaultUploader(self)
|
|
56
|
+
return uploader.upload(content=content, filename=filename, burn=burn, expiry=expiry, password=password)
|
|
57
|
+
|
|
58
|
+
def update_file(self, code: str, **kwargs) -> dict:
|
|
59
|
+
"""Update file settings (filename, burn_on_read, expiry_days)."""
|
|
60
|
+
with httpx.Client(base_url=self.base_url) as client:
|
|
61
|
+
resp = client.patch(
|
|
62
|
+
f"/api/files/{code}",
|
|
63
|
+
headers=self._get_headers(),
|
|
64
|
+
json=kwargs
|
|
65
|
+
)
|
|
66
|
+
resp.raise_for_status()
|
|
67
|
+
return resp.json()
|
|
68
|
+
|
|
69
|
+
def get_stats(self) -> dict:
|
|
70
|
+
"""Admin Only: Fetch global vault statistics."""
|
|
71
|
+
with httpx.Client(base_url=self.base_url) as client:
|
|
72
|
+
# Reuses the logic from your bot's show_stats
|
|
73
|
+
resp = client.get("/api/me", headers=self._get_headers())
|
|
74
|
+
return resp.json()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pathlib
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
class ConfigManager:
|
|
6
|
+
APP_NAME = "Akash"
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def get_config_dir() -> pathlib.Path:
|
|
10
|
+
"""Determines the OS-specific directory for config storage."""
|
|
11
|
+
home = pathlib.Path.home()
|
|
12
|
+
system = platform.system()
|
|
13
|
+
|
|
14
|
+
if system == "Windows":
|
|
15
|
+
path = pathlib.Path(os.getenv("APPDATA", home / "AppData/Roaming")) / ConfigManager.APP_NAME
|
|
16
|
+
elif system == "Darwin": # macOS
|
|
17
|
+
path = home / "Library/Application Support" / ConfigManager.APP_NAME
|
|
18
|
+
else: # Linux / Termux
|
|
19
|
+
path = home / ".akash"
|
|
20
|
+
|
|
21
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
return path
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_config_path() -> pathlib.Path:
|
|
26
|
+
return ConfigManager.get_config_dir() / "config.enc"
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def save(encrypted_bytes: bytes):
|
|
30
|
+
"""Writes the encrypted config to disk."""
|
|
31
|
+
path = ConfigManager.get_config_path()
|
|
32
|
+
with open(path, "wb") as f:
|
|
33
|
+
f.write(encrypted_bytes)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def load() -> dict:
|
|
37
|
+
"""Reads and decrypts the config file."""
|
|
38
|
+
from .crypto import SecureVault
|
|
39
|
+
import json
|
|
40
|
+
|
|
41
|
+
path = ConfigManager.get_config_path()
|
|
42
|
+
if not path.exists():
|
|
43
|
+
return {}
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
with open(path, "rb") as f:
|
|
47
|
+
encrypted_data = f.read()
|
|
48
|
+
|
|
49
|
+
decrypted_str = SecureVault.decrypt(encrypted_data)
|
|
50
|
+
return json.loads(decrypted_str)
|
|
51
|
+
except Exception:
|
|
52
|
+
return {}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
from cryptography.fernet import Fernet
|
|
5
|
+
from cryptography.hazmat.primitives import hashes
|
|
6
|
+
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
7
|
+
from .utils import get_device_id
|
|
8
|
+
|
|
9
|
+
class SecureVault:
|
|
10
|
+
@staticmethod
|
|
11
|
+
def _get_fernet(salt: bytes) -> Fernet:
|
|
12
|
+
"""Derives a deterministic key based on the hardware ID and a random salt."""
|
|
13
|
+
kdf = PBKDF2HMAC(
|
|
14
|
+
algorithm=hashes.SHA256(),
|
|
15
|
+
length=32,
|
|
16
|
+
salt=salt,
|
|
17
|
+
iterations=100000,
|
|
18
|
+
)
|
|
19
|
+
# Use hardware ID as the password for key derivation
|
|
20
|
+
key = base64.urlsafe_b64encode(kdf.derive(get_device_id().encode()))
|
|
21
|
+
return Fernet(key)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def encrypt(data: str) -> bytes:
|
|
25
|
+
"""Encrypts a string. Returns [16 bytes salt][ciphertext]."""
|
|
26
|
+
salt = os.urandom(16)
|
|
27
|
+
# 🌟 THE FIX: Call the helper via the Class name, not 'self'
|
|
28
|
+
f = SecureVault._get_fernet(salt)
|
|
29
|
+
return salt + f.encrypt(data.encode())
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def decrypt(token: bytes) -> str:
|
|
33
|
+
"""Decrypts a token using the hardware-bound key."""
|
|
34
|
+
try:
|
|
35
|
+
if not token:
|
|
36
|
+
return "{}"
|
|
37
|
+
salt, ciphertext = token[:16], token[16:]
|
|
38
|
+
f = SecureVault._get_fernet(salt)
|
|
39
|
+
return f.decrypt(ciphertext).decode()
|
|
40
|
+
except Exception:
|
|
41
|
+
# If hardware ID changed or file is corrupt, return empty config
|
|
42
|
+
return "{}"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class AkashError(Exception):
|
|
2
|
+
"""Base exception for all Akash CLI/SDK errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class AuthenticationError(AkashError):
|
|
6
|
+
"""Raised when API Key is missing, invalid, or locked."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class QuotaExceeded(AkashError):
|
|
10
|
+
"""Raised when the user's upload quota (429) has been reached."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class VaultFileNotFound(AkashError):
|
|
14
|
+
"""Raised when a specific file code cannot be found in the vault."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class UploadFailed(AkashError):
|
|
18
|
+
"""Raised when a file or text upload fails due to network or server issues."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class NetworkError(AkashError):
|
|
22
|
+
"""Raised for general connection issues (timeout, DNS, etc.)."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
class ConfigError(AkashError):
|
|
26
|
+
"""Raised when the local configuration is corrupt or unreadable."""
|
|
27
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class UserInfo:
|
|
7
|
+
owner: str
|
|
8
|
+
quota_used: int
|
|
9
|
+
quota_limit: int
|
|
10
|
+
allowed_domains: str
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class UploadResult:
|
|
14
|
+
status: str
|
|
15
|
+
file_code: str
|
|
16
|
+
embed_url: str
|
|
17
|
+
direct_url: str
|
|
18
|
+
input_type: str
|
|
19
|
+
dna: Dict[str, str]
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class FileInfo:
|
|
23
|
+
name: str
|
|
24
|
+
code: str
|
|
25
|
+
size: int
|
|
26
|
+
uploaded_at: Optional[datetime] = None
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import httpx, asyncio
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Callable, Dict, Any, List
|
|
5
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
|
+
from .models import UploadResult
|
|
7
|
+
from .exceptions import UploadFailed, QuotaExceeded
|
|
8
|
+
|
|
9
|
+
class VaultUploader:
|
|
10
|
+
def __init__(self, client):
|
|
11
|
+
self.client = client # Reference to AkashClient for headers/url
|
|
12
|
+
|
|
13
|
+
def upload(
|
|
14
|
+
self,
|
|
15
|
+
file_path: Optional[str] = None,
|
|
16
|
+
content: Optional[str] = None,
|
|
17
|
+
filename: Optional[str] = None,
|
|
18
|
+
burn: bool = False,
|
|
19
|
+
expiry: int = 0,
|
|
20
|
+
password: Optional[str] = None,
|
|
21
|
+
progress_callback: Optional[Callable[[int, int], None]] = None
|
|
22
|
+
) -> UploadResult:
|
|
23
|
+
"""
|
|
24
|
+
Standardizes both File and Text uploads into a single stream.
|
|
25
|
+
progress_callback signature: (bytes_read, total_bytes)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# 1. Prepare Headers based on V3 Power-Control Specs
|
|
29
|
+
headers = {
|
|
30
|
+
"x-vault-burn": "1" if burn else "0",
|
|
31
|
+
"x-vault-expiry": str(expiry),
|
|
32
|
+
}
|
|
33
|
+
if password:
|
|
34
|
+
headers["x-vault-password"] = password
|
|
35
|
+
|
|
36
|
+
url = f"{self.client.base_url}/api/upload"
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
if content is not None:
|
|
40
|
+
# --- TEXT SNIPPET MODE (JSON) ---
|
|
41
|
+
payload = {
|
|
42
|
+
"content": content,
|
|
43
|
+
"filename": filename or "snippet.txt"
|
|
44
|
+
}
|
|
45
|
+
resp = httpx.post(
|
|
46
|
+
url,
|
|
47
|
+
json=payload,
|
|
48
|
+
headers=self.client._get_headers(headers),
|
|
49
|
+
timeout=None
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
# --- FILE MODE (Multipart) ---
|
|
53
|
+
if not file_path or not os.path.exists(file_path):
|
|
54
|
+
raise UploadFailed(f"File not found: {file_path}")
|
|
55
|
+
|
|
56
|
+
file_size = os.path.getsize(file_path)
|
|
57
|
+
|
|
58
|
+
# Setup custom stream to track progress
|
|
59
|
+
def file_generator():
|
|
60
|
+
with open(file_path, "rb") as f:
|
|
61
|
+
chunk_size = 1024 * 1024 # 1MB chunks
|
|
62
|
+
bytes_read = 0
|
|
63
|
+
while chunk := f.read(chunk_size):
|
|
64
|
+
bytes_read += len(chunk)
|
|
65
|
+
if progress_callback:
|
|
66
|
+
progress_callback(bytes_read, file_size)
|
|
67
|
+
yield chunk
|
|
68
|
+
|
|
69
|
+
files = {"file": (os.path.basename(file_path), file_generator())}
|
|
70
|
+
|
|
71
|
+
resp = httpx.post(
|
|
72
|
+
url,
|
|
73
|
+
files=files,
|
|
74
|
+
headers=self.client._get_headers(headers),
|
|
75
|
+
timeout=None
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if resp.status_code == 429:
|
|
79
|
+
raise QuotaExceeded("Monthly upload quota reached.")
|
|
80
|
+
|
|
81
|
+
resp.raise_for_status()
|
|
82
|
+
d = resp.json()
|
|
83
|
+
|
|
84
|
+
return UploadResult(
|
|
85
|
+
status=d['status'],
|
|
86
|
+
file_code=d['file_code'],
|
|
87
|
+
embed_url=d['links']['embed'],
|
|
88
|
+
direct_url=d['links']['direct'],
|
|
89
|
+
input_type=d['input_type'],
|
|
90
|
+
dna=d.get('dna', {})
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
except httpx.HTTPStatusError as e:
|
|
94
|
+
raise UploadFailed(f"Server rejected upload ({e.response.status_code}): {e.response.text}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
raise UploadFailed(f"Unexpected error during upload: {e}")
|
|
97
|
+
|
|
98
|
+
def upload_directory(self, folder_path: str, max_workers: int = 3) -> List[UploadResult]:
|
|
99
|
+
"""Recursively uploads all files in a folder using a thread pool."""
|
|
100
|
+
# Convert string path to Path object
|
|
101
|
+
root = Path(folder_path)
|
|
102
|
+
if not root.is_dir():
|
|
103
|
+
raise UploadFailed(f"Source is not a directory: {folder_path}")
|
|
104
|
+
|
|
105
|
+
# Find all files recursively (excluding hidden files/folders)
|
|
106
|
+
paths = [p for p in root.glob("**/*") if p.is_file() and not p.name.startswith('.')]
|
|
107
|
+
results = []
|
|
108
|
+
|
|
109
|
+
# Using ThreadPoolExecutor for parallel I/O bound tasks
|
|
110
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
111
|
+
# We wrap the self.upload calls into the executor
|
|
112
|
+
# Note: We don't pass a progress_callback here to avoid UI overlap
|
|
113
|
+
# (The CLI 'bulk' command will handle the UI instead)
|
|
114
|
+
futures = [executor.submit(self.upload, file_path=str(p)) for p in paths]
|
|
115
|
+
|
|
116
|
+
for f in futures:
|
|
117
|
+
try:
|
|
118
|
+
results.append(f.result())
|
|
119
|
+
except Exception as e:
|
|
120
|
+
# Log error but continue with other files
|
|
121
|
+
print(f"Error uploading a file in batch: {e}")
|
|
122
|
+
|
|
123
|
+
return results
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import hashlib
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
def get_device_id() -> str:
|
|
6
|
+
"""Generates a unique hardware fingerprint."""
|
|
7
|
+
# Combine node (MAC address) and system info for a stable ID
|
|
8
|
+
raw_id = f"{uuid.getnode()}-{platform.node()}-{platform.processor()}"
|
|
9
|
+
return hashlib.sha256(raw_id.encode()).hexdigest()
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: akashcli
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Enterprise Developer SDK and CLI for Akash Vault
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx
|
|
8
|
+
Requires-Dist: typer[all]
|
|
9
|
+
Requires-Dist: rich
|
|
10
|
+
Requires-Dist: cryptography
|
|
11
|
+
|
|
12
|
+
To get started with **AkashCLI**, your users should follow these steps. You can provide this guide as a `README.md` or a "Getting Started" message in your bot.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# 🚀 Akash Vault: Getting Started Guide
|
|
17
|
+
|
|
18
|
+
**AkashCLI** is the professional command-line interface and SDK for interacting with your E2E encrypted vault. Follow these steps to install and secure your first files.
|
|
19
|
+
|
|
20
|
+
## 1. Prerequisites
|
|
21
|
+
Ensure you have **Python 3.10+** and `pip` installed on your machine (Linux, Termux, Mac, or Windows).
|
|
22
|
+
|
|
23
|
+
## 2. Installation
|
|
24
|
+
Since this is a custom package, users can install it directly from your source code or repository:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone the repository
|
|
28
|
+
git clone https://github.com/juniorsir/akashcli
|
|
29
|
+
cd akashcli
|
|
30
|
+
|
|
31
|
+
# Install in editable mode
|
|
32
|
+
pip install . --break-system-packages
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 3. First-Time Setup (Authentication)
|
|
38
|
+
Before using the vault, you must link your device using an API Key generated from the [Akash Telegram Bot](https://t.me/EveryDL_bot).
|
|
39
|
+
|
|
40
|
+
1. Open the Bot and go to **👤 My Account** -> **🔑 Generate API Key**.
|
|
41
|
+
2. In your terminal, run:
|
|
42
|
+
```bash
|
|
43
|
+
akash login
|
|
44
|
+
```
|
|
45
|
+
3. Enter your API Key when prompted. Your key will be **encrypted and bound to your hardware** automatically.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 4. Daily Workflow Commands
|
|
50
|
+
|
|
51
|
+
### 📤 Uploading Content
|
|
52
|
+
**Upload a file:**
|
|
53
|
+
```bash
|
|
54
|
+
akash upload movie.mp4
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Upload a secret text snippet:**
|
|
58
|
+
```bash
|
|
59
|
+
akash upload --text "My secret server password is 1234" --name "passwords.txt"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Upload with security flags:**
|
|
63
|
+
```bash
|
|
64
|
+
# Delete automatically after the first person views it
|
|
65
|
+
akash upload --burn secret_doc.pdf
|
|
66
|
+
|
|
67
|
+
# Set a 7-day expiry
|
|
68
|
+
akash upload --expiry 7 data_backup.zip
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 📦 Managing the Vault
|
|
72
|
+
**List your most recent files:**
|
|
73
|
+
```bash
|
|
74
|
+
akash files
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Get file details or secure links:**
|
|
78
|
+
```bash
|
|
79
|
+
akash stream FILE_CODE
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Delete a file permanently:**
|
|
83
|
+
```bash
|
|
84
|
+
akash delete FILE_CODE
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 5. Professional Features
|
|
90
|
+
|
|
91
|
+
### 🚀 Bulk Vaulting
|
|
92
|
+
Vault an entire folder of documents or images at once:
|
|
93
|
+
```bash
|
|
94
|
+
akash bulk ./my_documents_folder
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 🎬 Native Streaming
|
|
98
|
+
Stream a vaulted video directly into **VLC Media Player** without downloading:
|
|
99
|
+
```bash
|
|
100
|
+
akash stream FILE_CODE --vlc
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 📊 System Health
|
|
104
|
+
Check your remaining quota and vault status:
|
|
105
|
+
```bash
|
|
106
|
+
akash admin
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 6. Using the SDK (For Developers)
|
|
112
|
+
If you are building your own Python scripts, you can import the library directly:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from akashcli import AkashClient
|
|
116
|
+
|
|
117
|
+
# Initialize client (uses your encrypted local config)
|
|
118
|
+
client = AkashClient()
|
|
119
|
+
|
|
120
|
+
# Vault a log file
|
|
121
|
+
result = client.upload_file("system.log", expiry=30)
|
|
122
|
+
print(f"File secured! Access it at: {result.embed_url}")
|
|
123
|
+
|
|
124
|
+
# List all your files programmatically
|
|
125
|
+
for file in client.list_files():
|
|
126
|
+
print(f"{file.name} -> {file.code}")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 💡 Troubleshooting
|
|
132
|
+
* **Access Denied?** Ensure you have set your API Key domains to `*` in the Telegram Bot under "Edit Domains."
|
|
133
|
+
* **VLC not opening?** Ensure the `vlc` command is available in your system's PATH.
|
|
134
|
+
* **Forgot your key?** Run `akash login` again to overwrite the old session.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
**🛡️ Powered by Akash Enterprise Core.**
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
akashcli/__init__.py
|
|
4
|
+
akashcli/auth.py
|
|
5
|
+
akashcli/cli.py
|
|
6
|
+
akashcli/client.py
|
|
7
|
+
akashcli/config.py
|
|
8
|
+
akashcli/crypto.py
|
|
9
|
+
akashcli/exceptions.py
|
|
10
|
+
akashcli/models.py
|
|
11
|
+
akashcli/uploader.py
|
|
12
|
+
akashcli/utils.py
|
|
13
|
+
akashcli.egg-info/PKG-INFO
|
|
14
|
+
akashcli.egg-info/SOURCES.txt
|
|
15
|
+
akashcli.egg-info/dependency_links.txt
|
|
16
|
+
akashcli.egg-info/entry_points.txt
|
|
17
|
+
akashcli.egg-info/requires.txt
|
|
18
|
+
akashcli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
akashcli
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "akashcli"
|
|
3
|
+
version = "3.0.0"
|
|
4
|
+
description = "Enterprise Developer SDK and CLI for Akash Vault"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx",
|
|
9
|
+
"typer[all]",
|
|
10
|
+
"rich",
|
|
11
|
+
"cryptography",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
akash = "akashcli.cli:app"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["setuptools", "wheel"]
|
|
19
|
+
build-backend = "setuptools.build_meta"
|
akashcli-3.0.0/setup.cfg
ADDED