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.
@@ -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://console.sference.com`) |
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://console.sference.com`) |
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.7"
3
+ version = "0.0.8"
4
4
  description = "sference command-line interface"
5
5
  readme = "README.md"
6
- requires-python = ">=3.12"
6
+ requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "typer>=0.24.1",
9
- "sference-sdk>=0.0.7",
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://console.sference.com"
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://console.sference.com).",
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
- wait: bool = typer.Option(False, "--wait/--no-wait"),
356
- poll_ms: int = typer.Option(500, "--poll-ms", help="Polling interval when --wait is enabled."),
357
- timeout_s: int = typer.Option(60, "--timeout-s", help="Timeout in seconds when --wait is enabled."),
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
- poll_ms: int = typer.Option(500, "--poll-ms", help="Polling interval while waiting for completion."),
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(10, poll_ms) / 1000.0)
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
- poll_ms: int = typer.Option(5000, "--poll-ms", help="Long-poll wait_ms when catching up (max 30000)."),
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=min(poll_ms, 30000),
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(1, poll_ms) / 1000.0)
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