hypercli-cli 1.0.4__tar.gz → 1.0.6__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.
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/PKG-INFO +4 -1
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/claw.py +4 -0
- hypercli_cli-1.0.6/hypercli_cli/embed.py +142 -0
- hypercli_cli-1.0.6/hypercli_cli/stt.py +105 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/pyproject.toml +5 -1
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/.gitignore +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/README.md +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/__init__.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/agents.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/billing.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/cli.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/comfyui.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/flow.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/instances.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/jobs.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/keys.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/onboard.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/output.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/renders.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/tui/__init__.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/tui/job_monitor.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/user.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/voice.py +0 -0
- {hypercli_cli-1.0.4 → hypercli_cli-1.0.6}/hypercli_cli/wallet.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypercli-cli
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: CLI for HyperCLI - GPU orchestration and LLM API
|
|
5
5
|
Project-URL: Homepage, https://hypercli.com
|
|
6
6
|
Project-URL: Documentation, https://docs.hypercli.com
|
|
@@ -18,6 +18,7 @@ Requires-Dist: websocket-client>=1.6.0
|
|
|
18
18
|
Provides-Extra: all
|
|
19
19
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'all'
|
|
20
20
|
Requires-Dist: eth-account>=0.13.0; extra == 'all'
|
|
21
|
+
Requires-Dist: faster-whisper>=1.1.0; extra == 'all'
|
|
21
22
|
Requires-Dist: hypercli-sdk[comfyui]>=1.0.0; extra == 'all'
|
|
22
23
|
Requires-Dist: web3>=7.0.0; extra == 'all'
|
|
23
24
|
Requires-Dist: x402[evm,httpx]>=2.0.0; extra == 'all'
|
|
@@ -26,6 +27,8 @@ Requires-Dist: hypercli-sdk[comfyui]>=1.0.0; extra == 'comfyui'
|
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
28
29
|
Requires-Dist: ruff>=0.3.0; extra == 'dev'
|
|
30
|
+
Provides-Extra: stt
|
|
31
|
+
Requires-Dist: faster-whisper>=1.1.0; extra == 'stt'
|
|
29
32
|
Provides-Extra: wallet
|
|
30
33
|
Requires-Dist: argon2-cffi>=25.0.0; extra == 'wallet'
|
|
31
34
|
Requires-Dist: eth-account>=0.13.0; extra == 'wallet'
|
|
@@ -9,6 +9,8 @@ from rich.table import Table
|
|
|
9
9
|
|
|
10
10
|
from .onboard import onboard as _onboard_fn
|
|
11
11
|
from .voice import app as voice_app
|
|
12
|
+
from .stt import app as stt_app
|
|
13
|
+
from .embed import app as embed_app
|
|
12
14
|
|
|
13
15
|
app = typer.Typer(help="HyperClaw inference commands")
|
|
14
16
|
console = Console()
|
|
@@ -16,6 +18,8 @@ console = Console()
|
|
|
16
18
|
# Register subcommands
|
|
17
19
|
app.command("onboard")(_onboard_fn)
|
|
18
20
|
app.add_typer(voice_app, name="voice")
|
|
21
|
+
app.add_typer(stt_app, name="stt")
|
|
22
|
+
app.add_typer(embed_app, name="embed")
|
|
19
23
|
|
|
20
24
|
# Check if wallet dependencies are available
|
|
21
25
|
try:
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""HyperClaw Embed — text embeddings via HyperClaw API."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
app = typer.Typer(help="Text embeddings via HyperClaw API (qwen3-embedding-4b)")
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
HYPERCLI_DIR = Path.home() / ".hypercli"
|
|
13
|
+
CLAW_KEY_PATH = HYPERCLI_DIR / "claw-key.json"
|
|
14
|
+
PROD_API_BASE = "https://api.hyperclaw.app"
|
|
15
|
+
DEV_API_BASE = "https://api.dev.hyperclaw.app"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_api_key(key: str | None) -> str:
|
|
19
|
+
if key:
|
|
20
|
+
return key
|
|
21
|
+
if CLAW_KEY_PATH.exists():
|
|
22
|
+
with open(CLAW_KEY_PATH) as f:
|
|
23
|
+
k = json.load(f).get("key", "")
|
|
24
|
+
if k:
|
|
25
|
+
return k
|
|
26
|
+
console.print("[red]❌ No API key found.[/red]")
|
|
27
|
+
console.print("Pass [bold]--key sk-...[/bold] or run [bold]hyper claw subscribe[/bold]")
|
|
28
|
+
raise typer.Exit(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command("text")
|
|
32
|
+
def embed_text(
|
|
33
|
+
text: str = typer.Argument(..., help="Text to embed"),
|
|
34
|
+
model: str = typer.Option("qwen3-embedding-4b", "--model", "-m", help="Embedding model"),
|
|
35
|
+
key: str = typer.Option(None, "--key", "-k", help="API key (sk-...)"),
|
|
36
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
37
|
+
json_output: bool = typer.Option(False, "--json", help="Output full JSON response"),
|
|
38
|
+
output: Path = typer.Option(None, "--output", "-o", help="Write embeddings to file"),
|
|
39
|
+
):
|
|
40
|
+
"""Generate embeddings for text.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
hyper claw embed text "Hello world"
|
|
44
|
+
hyper claw embed text "Test" --json
|
|
45
|
+
hyper claw embed text "Document chunk" -o embedding.json
|
|
46
|
+
"""
|
|
47
|
+
api_key = _get_api_key(key)
|
|
48
|
+
api_base = DEV_API_BASE if dev else PROD_API_BASE
|
|
49
|
+
url = f"{api_base}/v1/embeddings"
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
resp = httpx.post(
|
|
53
|
+
url,
|
|
54
|
+
json={"model": model, "input": text},
|
|
55
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
56
|
+
timeout=30.0,
|
|
57
|
+
)
|
|
58
|
+
resp.raise_for_status()
|
|
59
|
+
data = resp.json()
|
|
60
|
+
except httpx.HTTPError as e:
|
|
61
|
+
console.print(f"[red]❌ Embedding request failed: {e}[/red]")
|
|
62
|
+
raise typer.Exit(1)
|
|
63
|
+
|
|
64
|
+
embedding = data["data"][0]["embedding"]
|
|
65
|
+
dims = len(embedding)
|
|
66
|
+
usage = data.get("usage", {})
|
|
67
|
+
tokens = usage.get("total_tokens", 0)
|
|
68
|
+
|
|
69
|
+
if json_output:
|
|
70
|
+
result = json.dumps(data, indent=2)
|
|
71
|
+
if output:
|
|
72
|
+
output.write_text(result)
|
|
73
|
+
console.print(f"[green]✅ Written to {output} ({dims} dimensions, {tokens} tokens)[/green]")
|
|
74
|
+
else:
|
|
75
|
+
print(result)
|
|
76
|
+
else:
|
|
77
|
+
console.print(f"[green]✅ Embedded ({dims} dimensions, {tokens} tokens)[/green]")
|
|
78
|
+
console.print(f"[dim]Model: {data.get('model', model)}[/dim]")
|
|
79
|
+
console.print(f"[dim]First 5: {embedding[:5]}[/dim]")
|
|
80
|
+
if output:
|
|
81
|
+
output.write_text(json.dumps(embedding))
|
|
82
|
+
console.print(f"[green]Saved to {output}[/green]")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@app.command("test")
|
|
86
|
+
def embed_test(
|
|
87
|
+
key: str = typer.Option(None, "--key", "-k", help="API key (sk-...)"),
|
|
88
|
+
dev: bool = typer.Option(False, "--dev", help="Use dev API"),
|
|
89
|
+
):
|
|
90
|
+
"""Quick test to verify embedding endpoint works.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
hyper claw embed test
|
|
94
|
+
hyper claw embed test --dev
|
|
95
|
+
"""
|
|
96
|
+
api_key = _get_api_key(key)
|
|
97
|
+
api_base = DEV_API_BASE if dev else PROD_API_BASE
|
|
98
|
+
url = f"{api_base}/v1/embeddings"
|
|
99
|
+
|
|
100
|
+
test_texts = [
|
|
101
|
+
"The quick brown fox jumps over the lazy dog.",
|
|
102
|
+
"GPU orchestration and cloud computing infrastructure.",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
console.print(f"[bold]Testing embedding endpoint: {url}[/bold]\n")
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
resp = httpx.post(
|
|
109
|
+
url,
|
|
110
|
+
json={"model": "qwen3-embedding-4b", "input": test_texts},
|
|
111
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
112
|
+
timeout=30.0,
|
|
113
|
+
)
|
|
114
|
+
resp.raise_for_status()
|
|
115
|
+
data = resp.json()
|
|
116
|
+
except httpx.HTTPError as e:
|
|
117
|
+
console.print(f"[red]❌ FAIL: {e}[/red]")
|
|
118
|
+
raise typer.Exit(1)
|
|
119
|
+
|
|
120
|
+
embeddings = data.get("data", [])
|
|
121
|
+
if len(embeddings) != 2:
|
|
122
|
+
console.print(f"[red]❌ Expected 2 embeddings, got {len(embeddings)}[/red]")
|
|
123
|
+
raise typer.Exit(1)
|
|
124
|
+
|
|
125
|
+
dims = len(embeddings[0]["embedding"])
|
|
126
|
+
usage = data.get("usage", {})
|
|
127
|
+
|
|
128
|
+
# Compute cosine similarity
|
|
129
|
+
import math
|
|
130
|
+
v1 = embeddings[0]["embedding"]
|
|
131
|
+
v2 = embeddings[1]["embedding"]
|
|
132
|
+
dot = sum(a * b for a, b in zip(v1, v2))
|
|
133
|
+
mag1 = math.sqrt(sum(a * a for a in v1))
|
|
134
|
+
mag2 = math.sqrt(sum(b * b for b in v2))
|
|
135
|
+
cosine_sim = dot / (mag1 * mag2) if mag1 and mag2 else 0
|
|
136
|
+
|
|
137
|
+
console.print(f"[green]✅ PASS[/green]")
|
|
138
|
+
console.print(f" Model: {data.get('model', 'qwen3-embedding-4b')}")
|
|
139
|
+
console.print(f" Dimensions: {dims}")
|
|
140
|
+
console.print(f" Tokens: {usage.get('total_tokens', '?')}")
|
|
141
|
+
console.print(f" Cosine similarity: {cosine_sim:.4f}")
|
|
142
|
+
console.print(f" (Two unrelated sentences should be < 0.9)")
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""HyperClaw STT — local speech-to-text via faster-whisper."""
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="Speech-to-text via faster-whisper (local, no API key)")
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
# Lazy import to avoid crashing when faster-whisper isn't installed
|
|
12
|
+
_model_cache = {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_model(model_size: str, device: str, compute_type: str):
|
|
16
|
+
"""Get or create a cached whisper model."""
|
|
17
|
+
key = (model_size, device, compute_type)
|
|
18
|
+
if key not in _model_cache:
|
|
19
|
+
try:
|
|
20
|
+
from faster_whisper import WhisperModel
|
|
21
|
+
except ImportError:
|
|
22
|
+
console.print("[red]❌ faster-whisper not installed.[/red]")
|
|
23
|
+
console.print("Install with: [bold]pip install 'hypercli-cli[stt]'[/bold]")
|
|
24
|
+
console.print("Or: [bold]pip install 'hypercli-cli[all]'[/bold]")
|
|
25
|
+
raise typer.Exit(1)
|
|
26
|
+
_model_cache[key] = WhisperModel(model_size, device=device, compute_type=compute_type)
|
|
27
|
+
return _model_cache[key]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("transcribe")
|
|
31
|
+
def transcribe(
|
|
32
|
+
audio_file: Path = typer.Argument(..., help="Audio file to transcribe (wav, mp3, ogg, m4a, etc.)"),
|
|
33
|
+
model: str = typer.Option("turbo", "--model", "-m", help="Whisper model: tiny, base, small, medium, large-v3, turbo"),
|
|
34
|
+
language: str = typer.Option(None, "--language", "-l", help="Language code (e.g. en, de, fr). Auto-detect if omitted."),
|
|
35
|
+
device: str = typer.Option("auto", "--device", "-d", help="Device: auto, cpu, cuda"),
|
|
36
|
+
compute_type: str = typer.Option("auto", "--compute", help="Compute type: auto, int8, float16, float32"),
|
|
37
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON with timestamps"),
|
|
38
|
+
output: Path = typer.Option(None, "--output", "-o", help="Write transcript to file"),
|
|
39
|
+
):
|
|
40
|
+
"""Transcribe audio to text using faster-whisper (runs locally).
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
hyper claw stt transcribe voice.ogg
|
|
44
|
+
hyper claw stt transcribe meeting.mp3 --model large-v3 --language en
|
|
45
|
+
hyper claw stt transcribe audio.wav --json -o transcript.json
|
|
46
|
+
"""
|
|
47
|
+
if not audio_file.exists():
|
|
48
|
+
console.print(f"[red]❌ File not found: {audio_file}[/red]")
|
|
49
|
+
raise typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
# Auto-select compute type based on device
|
|
52
|
+
if compute_type == "auto":
|
|
53
|
+
compute_type = "int8" if device == "cpu" else "float16"
|
|
54
|
+
if device == "auto":
|
|
55
|
+
try:
|
|
56
|
+
import torch
|
|
57
|
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
|
58
|
+
except ImportError:
|
|
59
|
+
device = "cpu"
|
|
60
|
+
if device == "cpu" and compute_type == "float16":
|
|
61
|
+
compute_type = "int8"
|
|
62
|
+
|
|
63
|
+
console.print(f"[dim]Model: {model} | Device: {device} | Compute: {compute_type}[/dim]")
|
|
64
|
+
console.print(f"[dim]File: {audio_file} ({audio_file.stat().st_size / 1024:.1f} KB)[/dim]")
|
|
65
|
+
|
|
66
|
+
whisper_model = _get_model(model, device, compute_type)
|
|
67
|
+
|
|
68
|
+
kwargs = {}
|
|
69
|
+
if language:
|
|
70
|
+
kwargs["language"] = language
|
|
71
|
+
|
|
72
|
+
segments, info = whisper_model.transcribe(str(audio_file), **kwargs)
|
|
73
|
+
|
|
74
|
+
if not language:
|
|
75
|
+
console.print(f"[dim]Detected language: {info.language} (p={info.language_probability:.2f})[/dim]")
|
|
76
|
+
|
|
77
|
+
if json_output:
|
|
78
|
+
import json
|
|
79
|
+
results = []
|
|
80
|
+
for seg in segments:
|
|
81
|
+
results.append({
|
|
82
|
+
"start": round(seg.start, 3),
|
|
83
|
+
"end": round(seg.end, 3),
|
|
84
|
+
"text": seg.text.strip(),
|
|
85
|
+
})
|
|
86
|
+
output_text = json.dumps({
|
|
87
|
+
"language": info.language,
|
|
88
|
+
"language_probability": round(info.language_probability, 3),
|
|
89
|
+
"duration": round(info.duration, 3),
|
|
90
|
+
"segments": results,
|
|
91
|
+
"text": " ".join(r["text"] for r in results),
|
|
92
|
+
}, indent=2, ensure_ascii=False)
|
|
93
|
+
else:
|
|
94
|
+
parts = []
|
|
95
|
+
for seg in segments:
|
|
96
|
+
parts.append(seg.text.strip())
|
|
97
|
+
output_text = " ".join(parts)
|
|
98
|
+
|
|
99
|
+
if output:
|
|
100
|
+
output.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
output.write_text(output_text)
|
|
102
|
+
console.print(f"[green]✅ Written to {output}[/green]")
|
|
103
|
+
else:
|
|
104
|
+
# Print to stdout (useful for piping)
|
|
105
|
+
print(output_text)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hypercli-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.6"
|
|
8
8
|
description = "CLI for HyperCLI - GPU orchestration and LLM API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -33,12 +33,16 @@ wallet = [
|
|
|
33
33
|
"argon2-cffi>=25.0.0",
|
|
34
34
|
"qrcode[pil]>=7.4.0",
|
|
35
35
|
]
|
|
36
|
+
stt = [
|
|
37
|
+
"faster-whisper>=1.1.0",
|
|
38
|
+
]
|
|
36
39
|
all = [
|
|
37
40
|
"hypercli-sdk[comfyui]>=1.0.0",
|
|
38
41
|
"x402[httpx,evm]>=2.0.0",
|
|
39
42
|
"eth-account>=0.13.0",
|
|
40
43
|
"web3>=7.0.0",
|
|
41
44
|
"argon2-cffi>=25.0.0",
|
|
45
|
+
"faster-whisper>=1.1.0",
|
|
42
46
|
]
|
|
43
47
|
dev = [
|
|
44
48
|
"pytest>=8.0.0",
|
|
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
|
|
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
|