sference-cli 0.0.7__tar.gz → 0.0.8__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.
- sference_cli-0.0.7/README.md → sference_cli-0.0.8/PKG-INFO +20 -1
- sference_cli-0.0.7/PKG-INFO → sference_cli-0.0.8/README.md +11 -10
- {sference_cli-0.0.7 → sference_cli-0.0.8}/pyproject.toml +3 -3
- {sference_cli-0.0.7 → sference_cli-0.0.8}/sference_cli/main.py +30 -39
- {sference_cli-0.0.7 → sference_cli-0.0.8}/.gitignore +0 -0
- {sference_cli-0.0.7 → sference_cli-0.0.8}/sference_cli/__init__.py +0 -0
- {sference_cli-0.0.7 → sference_cli-0.0.8}/sference_cli/stream_cache.py +0 -0
|
@@ -1,9 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sference-cli
|
|
3
|
+
Version: 0.0.8
|
|
4
|
+
Summary: sference command-line interface
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: sference-sdk>=0.0.8
|
|
7
|
+
Requires-Dist: typer>=0.24.1
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
1
10
|
# sference CLI
|
|
2
11
|
|
|
3
12
|
Command-line interface for the sference batch API (`sference`). It uses the Python SDK (`sference-sdk`) and is published on PyPI as `sference-cli`.
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
16
|
+
```bash
|
|
17
|
+
# One-line install (macOS / Linux)
|
|
18
|
+
curl -fsSL https://raw.githubusercontent.com/s-ference/sference/main/install.sh | sh
|
|
19
|
+
|
|
20
|
+
# Windows (PowerShell)
|
|
21
|
+
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/s-ference/sference/main/install.ps1 | iex"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install from PyPI:
|
|
25
|
+
|
|
7
26
|
```bash
|
|
8
27
|
pip install sference-cli
|
|
9
28
|
# or isolated install on PATH:
|
|
@@ -85,7 +104,7 @@ curl -sS "${SFERENCE_BASE_URL:-https://api.sference.com}/v1/responses/${RID}" \
|
|
|
85
104
|
|----------|---------|
|
|
86
105
|
| `SFERENCE_API_KEY` | API key (or JWT); overrides `~/.sference/credentials.json` |
|
|
87
106
|
| `SFERENCE_BASE_URL` | API base URL (default `https://api.sference.com`) |
|
|
88
|
-
| `SFERENCE_CONSOLE_URL` | Console URL for `auth login` browser step (default `https://
|
|
107
|
+
| `SFERENCE_CONSOLE_URL` | Console URL for `auth login` browser step (default `https://app.sference.com`) |
|
|
89
108
|
| `SFERENCE_STREAM_CACHE` | Optional path to the stream resumable-cache file (default `~/.sference/stream_cache.json`) |
|
|
90
109
|
| `SFERENCE_STREAM_CHECKPOINTS` | Optional path for **`responses tail`** event checkpoints (default `~/.sference/stream_checkpoints.json`) |
|
|
91
110
|
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: sference-cli
|
|
3
|
-
Version: 0.0.7
|
|
4
|
-
Summary: sference command-line interface
|
|
5
|
-
Requires-Python: >=3.12
|
|
6
|
-
Requires-Dist: sference-sdk>=0.0.7
|
|
7
|
-
Requires-Dist: typer>=0.24.1
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
|
|
10
1
|
# sference CLI
|
|
11
2
|
|
|
12
3
|
Command-line interface for the sference batch API (`sference`). It uses the Python SDK (`sference-sdk`) and is published on PyPI as `sference-cli`.
|
|
13
4
|
|
|
14
5
|
## Installation
|
|
15
6
|
|
|
7
|
+
```bash
|
|
8
|
+
# One-line install (macOS / Linux)
|
|
9
|
+
curl -fsSL https://raw.githubusercontent.com/s-ference/sference/main/install.sh | sh
|
|
10
|
+
|
|
11
|
+
# Windows (PowerShell)
|
|
12
|
+
powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/s-ference/sference/main/install.ps1 | iex"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or install from PyPI:
|
|
16
|
+
|
|
16
17
|
```bash
|
|
17
18
|
pip install sference-cli
|
|
18
19
|
# or isolated install on PATH:
|
|
@@ -94,7 +95,7 @@ curl -sS "${SFERENCE_BASE_URL:-https://api.sference.com}/v1/responses/${RID}" \
|
|
|
94
95
|
|----------|---------|
|
|
95
96
|
| `SFERENCE_API_KEY` | API key (or JWT); overrides `~/.sference/credentials.json` |
|
|
96
97
|
| `SFERENCE_BASE_URL` | API base URL (default `https://api.sference.com`) |
|
|
97
|
-
| `SFERENCE_CONSOLE_URL` | Console URL for `auth login` browser step (default `https://
|
|
98
|
+
| `SFERENCE_CONSOLE_URL` | Console URL for `auth login` browser step (default `https://app.sference.com`) |
|
|
98
99
|
| `SFERENCE_STREAM_CACHE` | Optional path to the stream resumable-cache file (default `~/.sference/stream_cache.json`) |
|
|
99
100
|
| `SFERENCE_STREAM_CHECKPOINTS` | Optional path for **`responses tail`** event checkpoints (default `~/.sference/stream_checkpoints.json`) |
|
|
100
101
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sference-cli"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.8"
|
|
4
4
|
description = "sference command-line interface"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"typer>=0.24.1",
|
|
9
|
-
"sference-sdk>=0.0.
|
|
9
|
+
"sference-sdk>=0.0.8",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
12
|
[project.scripts]
|
|
@@ -39,7 +39,7 @@ app.add_typer(models_app, name="models")
|
|
|
39
39
|
|
|
40
40
|
CREDENTIALS_PATH = Path.home() / ".sference" / "credentials.json"
|
|
41
41
|
|
|
42
|
-
DEFAULT_CONSOLE_URL = "https://
|
|
42
|
+
DEFAULT_CONSOLE_URL = "https://app.sference.com"
|
|
43
43
|
|
|
44
44
|
|
|
45
45
|
def _write_token(token: str) -> None:
|
|
@@ -61,14 +61,14 @@ def _read_token() -> str | None:
|
|
|
61
61
|
return None
|
|
62
62
|
|
|
63
63
|
|
|
64
|
-
def _client(base_url: Optional[str] = None) -> SferenceClient:
|
|
64
|
+
def _client(base_url: Optional[str] = None, *, timeout: float | None = None) -> SferenceClient:
|
|
65
65
|
# If the caller didn't explicitly pass --base-url, allow SFERENCE_BASE_URL
|
|
66
66
|
# to override the default.
|
|
67
67
|
env_base_url = os.environ.get("SFERENCE_BASE_URL")
|
|
68
68
|
if env_base_url and (base_url is None or base_url == "https://api.sference.com"):
|
|
69
69
|
_logger.info(f"Using SFERENCE_BASE_URL: {env_base_url}")
|
|
70
70
|
base_url = env_base_url
|
|
71
|
-
return SferenceClient(base_url=base_url, api_key=_read_token())
|
|
71
|
+
return SferenceClient(base_url=base_url, api_key=_read_token(), timeout=timeout)
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def _ensure_api_credential() -> None:
|
|
@@ -142,26 +142,6 @@ def _list_models(*, client: SferenceClient, as_json: bool) -> None:
|
|
|
142
142
|
typer.echo(json.dumps(payload, indent=None if as_json else 2))
|
|
143
143
|
|
|
144
144
|
|
|
145
|
-
def _wait_for_response(
|
|
146
|
-
client: SferenceClient, response_id: str, *, poll_ms: int, timeout_s: int
|
|
147
|
-
) -> Any:
|
|
148
|
-
"""Poll GET /v1/responses/{id} until terminal status or timeout."""
|
|
149
|
-
deadline = time.time() + max(1, timeout_s)
|
|
150
|
-
last: Any = None
|
|
151
|
-
while True:
|
|
152
|
-
last = _call_api(lambda: client.get_response(response_id))
|
|
153
|
-
if hasattr(last, "model_dump"):
|
|
154
|
-
d = last.model_dump()
|
|
155
|
-
status = d.get("status") if isinstance(d, dict) else None
|
|
156
|
-
else:
|
|
157
|
-
status = getattr(last, "status", None)
|
|
158
|
-
if status in ("completed", "failed", "cancelled"):
|
|
159
|
-
return last
|
|
160
|
-
if time.time() >= deadline:
|
|
161
|
-
return last
|
|
162
|
-
time.sleep(max(10, poll_ms) / 1000.0)
|
|
163
|
-
|
|
164
|
-
|
|
165
145
|
def _mvp_batch_window_only(value: str) -> str:
|
|
166
146
|
if value != "24h":
|
|
167
147
|
raise typer.BadParameter('Only "24h" is supported in MVP.', param_hint="--window")
|
|
@@ -268,7 +248,7 @@ def auth_login(
|
|
|
268
248
|
None,
|
|
269
249
|
"--console-url",
|
|
270
250
|
envvar="SFERENCE_CONSOLE_URL",
|
|
271
|
-
help="Console base URL for the browser step (default: https://
|
|
251
|
+
help="Console base URL for the browser step (default: https://app.sference.com).",
|
|
272
252
|
),
|
|
273
253
|
no_browser: bool = typer.Option(False, "--no-browser", help="Do not open a browser; print the URL and prompt only."),
|
|
274
254
|
) -> None:
|
|
@@ -352,34 +332,40 @@ def responses_create(
|
|
|
352
332
|
"--enable-thinking/--disable-thinking",
|
|
353
333
|
help="Qwen3: forward to tokenizer apply_chat_template. Omit for tokenizer default.",
|
|
354
334
|
),
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
335
|
+
background: bool = typer.Option(
|
|
336
|
+
False,
|
|
337
|
+
"--background/--no-background",
|
|
338
|
+
help="Submit asynchronously and return immediately. Default blocks until the response is terminal (matches POST /v1/responses).",
|
|
339
|
+
),
|
|
340
|
+
timeout: float = typer.Option(
|
|
341
|
+
600.0,
|
|
342
|
+
"--timeout",
|
|
343
|
+
help="HTTP read timeout in seconds.",
|
|
344
|
+
),
|
|
358
345
|
base_url: str = typer.Option("https://api.sference.com"),
|
|
359
346
|
) -> None:
|
|
360
347
|
_ensure_api_credential()
|
|
361
|
-
client = _client(base_url)
|
|
348
|
+
client = _client(base_url, timeout=timeout)
|
|
362
349
|
resp = _call_api(
|
|
363
350
|
lambda: client.create_response(
|
|
364
351
|
model=model,
|
|
365
352
|
input=[{"role": "user", "content": content}],
|
|
366
353
|
include_reasoning=include_reasoning,
|
|
367
354
|
enable_thinking=enable_thinking,
|
|
355
|
+
background=background,
|
|
368
356
|
)
|
|
369
357
|
)
|
|
370
|
-
if wait:
|
|
371
|
-
final = _wait_for_response(client, resp.id, poll_ms=poll_ms, timeout_s=timeout_s)
|
|
372
|
-
if getattr(final, "status", None) not in ("completed", "failed", "cancelled"):
|
|
373
|
-
_print(final.model_dump(), True)
|
|
374
|
-
raise typer.Exit(code=1)
|
|
375
|
-
resp = final
|
|
376
358
|
_print(resp.model_dump(), True)
|
|
377
359
|
|
|
378
360
|
|
|
379
361
|
@responses_app.command("result")
|
|
380
362
|
def responses_result(
|
|
381
363
|
id: str = typer.Option(..., "--id"),
|
|
382
|
-
|
|
364
|
+
poll_interval: float = typer.Option(
|
|
365
|
+
0.5,
|
|
366
|
+
"--poll-interval",
|
|
367
|
+
help="Seconds between status polls while waiting for completion.",
|
|
368
|
+
),
|
|
383
369
|
base_url: str = typer.Option("https://api.sference.com"),
|
|
384
370
|
) -> None:
|
|
385
371
|
"""Wait for a response to reach a terminal status and print the final JSON.
|
|
@@ -396,7 +382,7 @@ def responses_result(
|
|
|
396
382
|
if status in ("completed", "failed", "cancelled"):
|
|
397
383
|
_print(d, True)
|
|
398
384
|
return
|
|
399
|
-
time.sleep(max(
|
|
385
|
+
time.sleep(max(0.01, poll_interval))
|
|
400
386
|
except KeyboardInterrupt:
|
|
401
387
|
raise typer.Exit(code=130) from None
|
|
402
388
|
|
|
@@ -415,7 +401,11 @@ def responses_tail(
|
|
|
415
401
|
help="Ignore saved checkpoint and start from the latest events page.",
|
|
416
402
|
),
|
|
417
403
|
no_checkpoint: bool = typer.Option(False, "--no-checkpoint", help="Do not read or write checkpoints."),
|
|
418
|
-
|
|
404
|
+
poll_interval: float = typer.Option(
|
|
405
|
+
5.0,
|
|
406
|
+
"--poll-interval",
|
|
407
|
+
help="Seconds to long-poll while catching up (capped at 30s server-side).",
|
|
408
|
+
),
|
|
419
409
|
base_url: str = typer.Option("https://api.sference.com"),
|
|
420
410
|
as_json: bool = typer.Option(False, "--json"),
|
|
421
411
|
) -> None:
|
|
@@ -436,6 +426,7 @@ def responses_tail(
|
|
|
436
426
|
if not no_checkpoint and not from_latest:
|
|
437
427
|
last = load_checkpoint(bu, ck, consumer)
|
|
438
428
|
|
|
429
|
+
wait_ms = int(min(poll_interval, 30.0) * 1000)
|
|
439
430
|
try:
|
|
440
431
|
while True:
|
|
441
432
|
|
|
@@ -444,12 +435,12 @@ def responses_tail(
|
|
|
444
435
|
stream_id=stream_id,
|
|
445
436
|
limit=50,
|
|
446
437
|
starting_after=last,
|
|
447
|
-
wait_ms=
|
|
438
|
+
wait_ms=wait_ms,
|
|
448
439
|
)
|
|
449
440
|
|
|
450
441
|
page = _call_api(fetch)
|
|
451
442
|
if not page.data:
|
|
452
|
-
time.sleep(max(
|
|
443
|
+
time.sleep(max(0.001, poll_interval))
|
|
453
444
|
continue
|
|
454
445
|
for ev in page.data:
|
|
455
446
|
typer.echo(json.dumps(ev.model_dump(), default=str))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|