cdx-manager 0.9.4 → 0.9.5
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.
- package/README.md +6 -5
- package/changelogs/CHANGELOGS_0_9_5.md +45 -0
- package/checksums/release-archives.json +4 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +4 -4
- package/src/cli_commands.py +44 -26
- package/src/session_service.py +164 -48
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
5
|
**Run multiple Codex, Claude, Antigravity, and Ollama sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -69,6 +69,7 @@ One command to launch any session. Zero auth juggling.
|
|
|
69
69
|
- Primary source: recorded status fields on the session record.
|
|
70
70
|
- Codex live source: `codex app-server` JSON-RPC `account/rateLimits/read`, normalized into 5-hour, weekly, reset, credit, and plan fields.
|
|
71
71
|
- Fallback: `status-source` scans provider JSONL history files and terminal log transcripts, strips ANSI/OSC sequences, and extracts `usage%`, `5h remaining%`, and `week remaining%` via pattern matching.
|
|
72
|
+
- Status latency controls: use `cdx status --cached` to skip live probes, `cdx status --timeout SECONDS` for one command, or `CDX_STATUS_TIMEOUT_SECONDS` to lower the default Codex live-probe timeout.
|
|
72
73
|
- Claude status refreshes are cached briefly by default; pass `--refresh` to force a live rate-limit probe.
|
|
73
74
|
- On Linux, transcript capture uses the `util-linux` `script -c` command form.
|
|
74
75
|
- If `script` is unavailable, Codex launch falls back to running without transcript capture.
|
|
@@ -364,9 +365,9 @@ cdx history --summary --from 2026-05-01 --to 2026-05-28
|
|
|
364
365
|
| `cdx select --provider PROVIDER [--min-reasoning-effort minimal\|low\|medium\|high\|xhigh] [--min-power minimal\|low\|medium\|high\|xhigh] [--require-ready] [--refresh] --json` | Select a suitable session for headless automation |
|
|
365
366
|
| `cdx run [session] --cwd PATH (--prompt-file PATH\|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal\|low\|medium\|high\|xhigh] [--power minimal\|low\|medium\|high\|xhigh] [--permission MODE] [--timeout-seconds N] --json` | Run one headless task and return a stable JSON result |
|
|
366
367
|
| `cdx stats [name] [--since 7d\|today\|DATE] [--from DATE] [--to DATE] [--json]` | Aggregate launch counts, duration, and known headless token usage by session |
|
|
367
|
-
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions;
|
|
368
|
-
| `cdx status --small [--refresh]` / `cdx status -s [--refresh]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
369
|
-
| `cdx status <name> [--json] [--refresh]` | Show detailed usage breakdown for one session |
|
|
368
|
+
| `cdx status [--json] [--refresh\|--cached] [--timeout SECONDS]` | Show token usage table for all sessions; `--cached` skips live provider probes and returns only stored status |
|
|
369
|
+
| `cdx status --small [--refresh\|--cached] [--timeout SECONDS]` / `cdx status -s [--refresh\|--cached] [--timeout SECONDS]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
370
|
+
| `cdx status <name> [--json] [--refresh\|--cached] [--timeout SECONDS]` | Show detailed usage breakdown for one session; `--cached` avoids live provider refreshes |
|
|
370
371
|
| `cdx --help` | Show usage |
|
|
371
372
|
| `cdx --version` | Show version |
|
|
372
373
|
|
|
@@ -429,7 +430,7 @@ Most commands use a shared stderr JSON envelope for errors whenever `--json` is
|
|
|
429
430
|
"ok": false,
|
|
430
431
|
"error": {
|
|
431
432
|
"code": "invalid_usage",
|
|
432
|
-
"message": "Usage: cdx status [--json] [--refresh] | ...",
|
|
433
|
+
"message": "Usage: cdx status [--json] [--refresh|--cached] [--timeout SECONDS] | ...",
|
|
433
434
|
"exit_code": 1
|
|
434
435
|
}
|
|
435
436
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# CDX Manager 0.9.5
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
- Made per-session status lookup targeted instead of resolving every configured session.
|
|
6
|
+
- Added fast status controls for automation-heavy workspaces.
|
|
7
|
+
- Added a Logics release contract for the `cdx-manager` release workflow.
|
|
8
|
+
|
|
9
|
+
## Changes
|
|
10
|
+
|
|
11
|
+
### Faster per-session status
|
|
12
|
+
|
|
13
|
+
`cdx status <name>` now resolves only the named session. Previously the command collected all status rows and filtered afterward, so a detail lookup could still trigger live probes for every configured session.
|
|
14
|
+
|
|
15
|
+
### Cached and bounded status probes
|
|
16
|
+
|
|
17
|
+
`cdx status --cached` reads stored status only and skips live provider probes. This is intended for AGENTS prompts, health checks, and project scripts that need a quick snapshot without risking provider timeouts.
|
|
18
|
+
|
|
19
|
+
Live Codex status probes can also be bounded with:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
cdx status --timeout 1
|
|
23
|
+
CDX_STATUS_TIMEOUT_SECONDS=1 cdx status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The timeout applies to Codex live rate-limit probes while preserving the existing cached and fallback behavior.
|
|
27
|
+
|
|
28
|
+
### Release workflow contract
|
|
29
|
+
|
|
30
|
+
The repository now includes `logics/release/contract.json` and `logics/release/release-contract.v1.schema.json`. The contract records the expected version sources, changelog path, checksum gate, validation commands, GitHub release gate, npm publication, and PyPI publication checks.
|
|
31
|
+
|
|
32
|
+
## Validation
|
|
33
|
+
|
|
34
|
+
- `python -m unittest discover -s test -p 'test_session_service_py.py'`
|
|
35
|
+
- `python -m unittest discover -s test -p 'test_cli_py.py' -k 'status'`
|
|
36
|
+
- `npm run lint`
|
|
37
|
+
- JSON Schema validation for `logics/release/contract.json`
|
|
38
|
+
- `logics-manager lint --require-status`
|
|
39
|
+
- `logics-manager audit`
|
|
40
|
+
- `git diff --check`
|
|
41
|
+
- `node bin/cdx.js --version`
|
|
42
|
+
- `python3 bin/cdx --version`
|
|
43
|
+
- `npm --cache /private/tmp/cdx-npm-cache pack --dry-run`
|
|
44
|
+
- `python -m build`
|
|
45
|
+
- `python -m twine check dist/*`
|
|
@@ -80,6 +80,10 @@
|
|
|
80
80
|
"v0.9.3": {
|
|
81
81
|
"github_tarball_sha256": "cb3cfd9134447d1049b510836014001fb6c38a89de588adc8cdbb1fe5deb4d6d",
|
|
82
82
|
"github_zip_sha256": "056eb2d1a3f0fa721a7a0e597cd0409e496618cfa35791a78d4e184397596754"
|
|
83
|
+
},
|
|
84
|
+
"v0.9.4": {
|
|
85
|
+
"github_tarball_sha256": "fe1c96e612392130a98ec6d9856ec41fb0c055c1a274ee77f143b5e4b8c1fb2f",
|
|
86
|
+
"github_zip_sha256": "494b84aef93f796d78ef164c7b7670ba3dec1d81f66be966cb7a84d77d5ed6e3"
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
89
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -66,7 +66,7 @@ from .status_view import (
|
|
|
66
66
|
)
|
|
67
67
|
from .update_check import check_for_update, check_logics_manager_for_update
|
|
68
68
|
|
|
69
|
-
VERSION = "0.9.
|
|
69
|
+
VERSION = "0.9.5"
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
# ---------------------------------------------------------------------------
|
|
@@ -80,9 +80,9 @@ def _print_help(use_color=False):
|
|
|
80
80
|
_style("Usage:", "1", use_color),
|
|
81
81
|
f" {_style('cdx', '36', use_color)}",
|
|
82
82
|
f" {_style('cdx --json', '36', use_color)}",
|
|
83
|
-
f" {_style('cdx status [--json] [--refresh]', '36', use_color)}",
|
|
84
|
-
f" {_style('cdx status --small|-s [--refresh]', '36', use_color)}",
|
|
85
|
-
f" {_style('cdx status <name> [--json] [--refresh]', '36', use_color)}",
|
|
83
|
+
f" {_style('cdx status [--json] [--refresh|--cached] [--timeout SECONDS]', '36', use_color)}",
|
|
84
|
+
f" {_style('cdx status --small|-s [--refresh|--cached] [--timeout SECONDS]', '36', use_color)}",
|
|
85
|
+
f" {_style('cdx status <name> [--json] [--refresh|--cached] [--timeout SECONDS]', '36', use_color)}",
|
|
86
86
|
f" {_style('cdx next [--json] [--refresh]', '36', use_color)}",
|
|
87
87
|
f" {_style('cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json', '36', use_color)}",
|
|
88
88
|
f" {_style('cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json', '36', use_color)}",
|
package/src/cli_commands.py
CHANGED
|
@@ -52,7 +52,7 @@ from .update_check import LatestReleaseCheckError, fetch_latest_release, fetch_l
|
|
|
52
52
|
from .update_manager import build_update_plan, format_update_failure, run_update_plan, verify_updated_command
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
STATUS_USAGE = "Usage: cdx status [--json] [--refresh] | cdx status --small|-s [--refresh] | cdx status <name> [--json] [--refresh]"
|
|
55
|
+
STATUS_USAGE = "Usage: cdx status [--json] [--refresh|--cached] [--timeout SECONDS] | cdx status --small|-s [--refresh|--cached] [--timeout SECONDS] | cdx status <name> [--json] [--refresh|--cached] [--timeout SECONDS]"
|
|
56
56
|
DOCTOR_USAGE = "Usage: cdx doctor [--json]"
|
|
57
57
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
58
58
|
UPDATE_USAGE = "Usage: cdx update [--check] [--yes] [--json] [--version TAG]"
|
|
@@ -672,13 +672,13 @@ def _parse_next_args(args):
|
|
|
672
672
|
return {"json": parsed["json"], "refresh": parsed["refresh"]}
|
|
673
673
|
|
|
674
674
|
|
|
675
|
-
def _parse_timeout_seconds(value):
|
|
675
|
+
def _parse_timeout_seconds(value, usage=RUN_USAGE):
|
|
676
676
|
try:
|
|
677
677
|
parsed = float(value)
|
|
678
678
|
except (TypeError, ValueError) as error:
|
|
679
|
-
raise CdxError(
|
|
679
|
+
raise CdxError(usage) from error
|
|
680
680
|
if parsed <= 0:
|
|
681
|
-
raise CdxError(
|
|
681
|
+
raise CdxError(usage)
|
|
682
682
|
return parsed
|
|
683
683
|
|
|
684
684
|
|
|
@@ -2487,18 +2487,26 @@ def handle_status(rest, ctx):
|
|
|
2487
2487
|
"--small": {"key": "small", "type": "bool", "default": False},
|
|
2488
2488
|
"-s": {"key": "small", "type": "bool", "default": False},
|
|
2489
2489
|
"--refresh": {"key": "refresh", "type": "bool", "default": False},
|
|
2490
|
+
"--cached": {"key": "cached", "type": "bool", "default": False},
|
|
2491
|
+
"--timeout": {"key": "timeout", "type": "str", "default": None, "transform": lambda value: _parse_timeout_seconds(value, STATUS_USAGE)},
|
|
2490
2492
|
}, STATUS_USAGE, positionals_key="args", max_positionals=1)
|
|
2491
2493
|
if parsed["json"] and parsed["small"]:
|
|
2492
2494
|
raise CdxError(STATUS_USAGE)
|
|
2495
|
+
if parsed["refresh"] and parsed["cached"]:
|
|
2496
|
+
raise CdxError(STATUS_USAGE)
|
|
2493
2497
|
args = parsed["args"]
|
|
2494
2498
|
if len(args) == 1 and parsed["small"]:
|
|
2495
2499
|
raise CdxError(STATUS_USAGE)
|
|
2496
2500
|
|
|
2497
|
-
auth_refresh =
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2501
|
+
auth_refresh = (
|
|
2502
|
+
{"errors": []}
|
|
2503
|
+
if parsed["cached"]
|
|
2504
|
+
else _refresh_claude_auth_states(
|
|
2505
|
+
ctx["service"],
|
|
2506
|
+
target_names=args if len(args) == 1 else None,
|
|
2507
|
+
spawn_sync=ctx.get("spawn_sync"),
|
|
2508
|
+
env_override=ctx.get("env"),
|
|
2509
|
+
)
|
|
2502
2510
|
)
|
|
2503
2511
|
warnings = [
|
|
2504
2512
|
{
|
|
@@ -2508,11 +2516,15 @@ def handle_status(rest, ctx):
|
|
|
2508
2516
|
}
|
|
2509
2517
|
for item in auth_refresh.get("errors", [])
|
|
2510
2518
|
]
|
|
2511
|
-
refresh_result =
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2519
|
+
refresh_result = (
|
|
2520
|
+
{"errors": []}
|
|
2521
|
+
if parsed["cached"]
|
|
2522
|
+
else _refresh_claude_sessions(
|
|
2523
|
+
ctx["service"],
|
|
2524
|
+
ctx.get("refresh_fn"),
|
|
2525
|
+
target_names=args if len(args) == 1 else None,
|
|
2526
|
+
force=parsed["refresh"],
|
|
2527
|
+
)
|
|
2516
2528
|
)
|
|
2517
2529
|
refresh_errors = [
|
|
2518
2530
|
{
|
|
@@ -2532,26 +2544,32 @@ def handle_status(rest, ctx):
|
|
|
2532
2544
|
warnings.extend(_update_notice_warnings(ctx))
|
|
2533
2545
|
|
|
2534
2546
|
status_progress = None if parsed["json"] else _make_status_progress(ctx)
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2547
|
+
if len(args) == 1:
|
|
2548
|
+
row = ctx["service"]["get_status_row"](
|
|
2549
|
+
args[0],
|
|
2550
|
+
progress_callback=status_progress,
|
|
2551
|
+
force_refresh=parsed["refresh"],
|
|
2552
|
+
cache_only=parsed["cached"],
|
|
2553
|
+
status_timeout_seconds=parsed["timeout"],
|
|
2554
|
+
)
|
|
2540
2555
|
if parsed["json"]:
|
|
2541
|
-
_write_json(ctx, _json_success("status", "Collected
|
|
2556
|
+
_write_json(ctx, _json_success("status", f"Collected status for {args[0]}", warnings=warnings, session=row))
|
|
2542
2557
|
return 0
|
|
2543
|
-
ctx["out"](f"{
|
|
2558
|
+
ctx["out"](f"{_format_status_detail(row, use_color=ctx['use_color'])}\n")
|
|
2544
2559
|
_write_refresh_warnings(refresh_errors, ctx)
|
|
2545
2560
|
_write_update_notice(ctx)
|
|
2546
2561
|
return 0
|
|
2547
2562
|
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2563
|
+
rows = ctx["service"]["get_status_rows"](
|
|
2564
|
+
progress_callback=status_progress,
|
|
2565
|
+
force_refresh=parsed["refresh"],
|
|
2566
|
+
cache_only=parsed["cached"],
|
|
2567
|
+
status_timeout_seconds=parsed["timeout"],
|
|
2568
|
+
)
|
|
2551
2569
|
if parsed["json"]:
|
|
2552
|
-
_write_json(ctx, _json_success("status",
|
|
2570
|
+
_write_json(ctx, _json_success("status", "Collected session status rows", warnings=warnings, rows=rows))
|
|
2553
2571
|
return 0
|
|
2554
|
-
ctx["out"](f"{
|
|
2572
|
+
ctx["out"](f"{_format_status_rows(rows, use_color=ctx['use_color'], small=parsed['small'])}\n")
|
|
2555
2573
|
_write_refresh_warnings(refresh_errors, ctx)
|
|
2556
2574
|
_write_update_notice(ctx)
|
|
2557
2575
|
return 0
|
package/src/session_service.py
CHANGED
|
@@ -67,6 +67,7 @@ RESERVED_SESSION_NAMES = {
|
|
|
67
67
|
}
|
|
68
68
|
STATUS_CACHE_TTL_SECONDS = 60
|
|
69
69
|
CLAUDE_STATUS_CACHE_TTL_SECONDS = 10 * 60
|
|
70
|
+
STATUS_PROBE_TIMEOUT_SECONDS = 5
|
|
70
71
|
MAX_STATUS_WORKERS = 8
|
|
71
72
|
LAUNCH_POWER_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
72
73
|
LAUNCH_REASONING_EFFORT_VALUES = {"minimal", "low", "medium", "high", "xhigh"}
|
|
@@ -278,6 +279,18 @@ def _status_cache_ttl_seconds(session, ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
|
278
279
|
return ttl_seconds
|
|
279
280
|
|
|
280
281
|
|
|
282
|
+
def _parse_status_timeout_seconds(value):
|
|
283
|
+
if value in (None, ""):
|
|
284
|
+
return None
|
|
285
|
+
try:
|
|
286
|
+
parsed = float(value)
|
|
287
|
+
except (TypeError, ValueError):
|
|
288
|
+
return None
|
|
289
|
+
if parsed <= 0:
|
|
290
|
+
return None
|
|
291
|
+
return parsed
|
|
292
|
+
|
|
293
|
+
|
|
281
294
|
def _is_status_cache_fresh(session, ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
282
295
|
status = session.get("lastStatus") or {}
|
|
283
296
|
if _is_low_confidence_status_source(status):
|
|
@@ -433,7 +446,19 @@ def create_session_service(options=None):
|
|
|
433
446
|
env = options.get("env", os.environ)
|
|
434
447
|
base_dir = options.get("base_dir") or get_cdx_home(env)
|
|
435
448
|
store = options.get("store") or create_session_store(base_dir)
|
|
436
|
-
|
|
449
|
+
custom_codex_status_fetcher = options.get("fetchCodexRateLimits")
|
|
450
|
+
codex_status_fetcher = custom_codex_status_fetcher or fetch_codex_rate_limits
|
|
451
|
+
default_status_timeout_seconds = (
|
|
452
|
+
_parse_status_timeout_seconds(options.get("statusTimeoutSeconds"))
|
|
453
|
+
or _parse_status_timeout_seconds(env.get("CDX_STATUS_TIMEOUT_SECONDS"))
|
|
454
|
+
or STATUS_PROBE_TIMEOUT_SECONDS
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
def _fetch_codex_status(session, timeout_seconds=None):
|
|
458
|
+
if custom_codex_status_fetcher:
|
|
459
|
+
return custom_codex_status_fetcher(session)
|
|
460
|
+
timeout = timeout_seconds or default_status_timeout_seconds
|
|
461
|
+
return codex_status_fetcher(session, timeout=timeout)
|
|
437
462
|
|
|
438
463
|
def _get_session_root(name):
|
|
439
464
|
return os.path.join(base_dir, "profiles", _encode(name))
|
|
@@ -907,17 +932,28 @@ def create_session_service(options=None):
|
|
|
907
932
|
raise CdxError(f"Unknown session: {name}")
|
|
908
933
|
return store["list_launch_history"](session_name=name, limit=limit)
|
|
909
934
|
|
|
910
|
-
def _resolve_session_status(
|
|
935
|
+
def _resolve_session_status(
|
|
936
|
+
session,
|
|
937
|
+
force_refresh=False,
|
|
938
|
+
cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS,
|
|
939
|
+
cache_only=False,
|
|
940
|
+
status_timeout_seconds=None,
|
|
941
|
+
):
|
|
911
942
|
current_status = session.get("lastStatus")
|
|
912
943
|
if session.get("enabled", True) is False:
|
|
913
944
|
return current_status
|
|
945
|
+
if cache_only:
|
|
946
|
+
return current_status
|
|
914
947
|
if current_status and not force_refresh and _is_status_cache_fresh(session, ttl_seconds=cache_ttl_seconds):
|
|
915
948
|
return current_status
|
|
916
949
|
source_root = session.get("authHome") or _get_session_auth_home(
|
|
917
950
|
session["name"], session["provider"]
|
|
918
951
|
)
|
|
919
952
|
if session["provider"] == PROVIDER_CODEX and codex_status_fetcher:
|
|
920
|
-
live_status =
|
|
953
|
+
live_status = _fetch_codex_status(
|
|
954
|
+
{**session, "authHome": source_root},
|
|
955
|
+
timeout_seconds=status_timeout_seconds,
|
|
956
|
+
)
|
|
921
957
|
if live_status:
|
|
922
958
|
record_status(session["name"], live_status)
|
|
923
959
|
return live_status
|
|
@@ -983,41 +1019,133 @@ def create_session_service(options=None):
|
|
|
983
1019
|
raise CdxError(f"Unknown session: {name}")
|
|
984
1020
|
return updated
|
|
985
1021
|
|
|
986
|
-
def
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
s.get("
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
and not force_refresh
|
|
995
|
-
and _is_status_cache_fresh(s, ttl_seconds=cache_ttl_seconds)
|
|
996
|
-
)
|
|
1022
|
+
def _status_cache_hit(s, force_refresh=False, cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS, cache_only=False):
|
|
1023
|
+
return (
|
|
1024
|
+
cache_only
|
|
1025
|
+
or s.get("enabled", True) is False
|
|
1026
|
+
or (
|
|
1027
|
+
s.get("lastStatus")
|
|
1028
|
+
and not force_refresh
|
|
1029
|
+
and _is_status_cache_fresh(s, ttl_seconds=cache_ttl_seconds)
|
|
997
1030
|
)
|
|
1031
|
+
)
|
|
998
1032
|
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1033
|
+
def _resolve_row_session(
|
|
1034
|
+
s,
|
|
1035
|
+
force_refresh=False,
|
|
1036
|
+
cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS,
|
|
1037
|
+
cache_only=False,
|
|
1038
|
+
status_timeout_seconds=None,
|
|
1039
|
+
):
|
|
1040
|
+
status = _resolve_session_status(
|
|
1041
|
+
s,
|
|
1042
|
+
force_refresh=force_refresh,
|
|
1043
|
+
cache_ttl_seconds=cache_ttl_seconds,
|
|
1044
|
+
cache_only=cache_only,
|
|
1045
|
+
status_timeout_seconds=status_timeout_seconds,
|
|
1046
|
+
)
|
|
1047
|
+
return {
|
|
1048
|
+
**s,
|
|
1049
|
+
"lastStatus": status,
|
|
1050
|
+
"lastStatusAt": (status and status.get("updated_at")) or s.get("lastStatusAt"),
|
|
1002
1051
|
}
|
|
1052
|
+
|
|
1053
|
+
def _status_row_from_session(s):
|
|
1054
|
+
status = s.get("lastStatus")
|
|
1055
|
+
enabled = s.get("enabled", True) is not False
|
|
1056
|
+
row_status = status if enabled else None
|
|
1057
|
+
return {
|
|
1058
|
+
"session_name": s["name"],
|
|
1059
|
+
"provider": s["provider"],
|
|
1060
|
+
"enabled": enabled,
|
|
1061
|
+
"active": bool(_session_runtime(s["name"])) if enabled else False,
|
|
1062
|
+
"status": "enabled" if enabled else "disabled",
|
|
1063
|
+
"auth_status": (s.get("auth") or {}).get("status") or "unknown",
|
|
1064
|
+
"auth_checked_at": _to_local_iso((s.get("auth") or {}).get("lastCheckedAt")),
|
|
1065
|
+
"remaining_5h_pct": _normalize_pct_value(row_status.get("remaining_5h_pct")) if row_status else None,
|
|
1066
|
+
"remaining_week_pct": _normalize_pct_value(row_status.get("remaining_week_pct")) if row_status else None,
|
|
1067
|
+
"credits": row_status.get("credits") if row_status else None,
|
|
1068
|
+
"available_pct": _compute_available_pct(row_status),
|
|
1069
|
+
"reset_5h_at": row_status.get("reset_5h_at") if row_status else None,
|
|
1070
|
+
"reset_week_at": row_status.get("reset_week_at") if row_status else None,
|
|
1071
|
+
"reset_at": row_status.get("reset_at") if row_status else None,
|
|
1072
|
+
"updated_at": _to_local_iso(s.get("lastStatusAt")),
|
|
1073
|
+
"last_launched_at": _to_local_iso(s.get("lastLaunchedAt")),
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
def get_status_row(
|
|
1077
|
+
name,
|
|
1078
|
+
progress_callback=None,
|
|
1079
|
+
force_refresh=False,
|
|
1080
|
+
cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS,
|
|
1081
|
+
cache_only=False,
|
|
1082
|
+
status_timeout_seconds=None,
|
|
1083
|
+
):
|
|
1084
|
+
session = store["get_session"](name)
|
|
1085
|
+
if not session:
|
|
1086
|
+
raise CdxError(f"Unknown session: {name}")
|
|
1087
|
+
cache_hit = _status_cache_hit(
|
|
1088
|
+
session,
|
|
1089
|
+
force_refresh=force_refresh,
|
|
1090
|
+
cache_ttl_seconds=cache_ttl_seconds,
|
|
1091
|
+
cache_only=cache_only,
|
|
1092
|
+
)
|
|
1003
1093
|
if progress_callback:
|
|
1004
1094
|
progress_callback({
|
|
1005
1095
|
"event": "status_started",
|
|
1006
|
-
"session_count":
|
|
1007
|
-
"check_count":
|
|
1096
|
+
"session_count": 1,
|
|
1097
|
+
"check_count": 0 if cache_hit else 1,
|
|
1098
|
+
})
|
|
1099
|
+
if not cache_hit:
|
|
1100
|
+
progress_callback({
|
|
1101
|
+
"event": "session_started",
|
|
1102
|
+
"session_name": session["name"],
|
|
1103
|
+
"provider": session["provider"],
|
|
1104
|
+
})
|
|
1105
|
+
resolved = _resolve_row_session(
|
|
1106
|
+
session,
|
|
1107
|
+
force_refresh=force_refresh,
|
|
1108
|
+
cache_ttl_seconds=cache_ttl_seconds,
|
|
1109
|
+
cache_only=cache_only,
|
|
1110
|
+
status_timeout_seconds=status_timeout_seconds,
|
|
1111
|
+
)
|
|
1112
|
+
if progress_callback:
|
|
1113
|
+
progress_callback({
|
|
1114
|
+
"event": "session_finished",
|
|
1115
|
+
"session_name": session["name"],
|
|
1116
|
+
"has_status": bool(resolved.get("lastStatus")),
|
|
1117
|
+
"cache_hit": cache_hit,
|
|
1118
|
+
})
|
|
1119
|
+
progress_callback({
|
|
1120
|
+
"event": "status_finished",
|
|
1121
|
+
"row_count": 1,
|
|
1008
1122
|
})
|
|
1123
|
+
return _status_row_from_session(resolved)
|
|
1124
|
+
|
|
1125
|
+
def get_status_rows(
|
|
1126
|
+
progress_callback=None,
|
|
1127
|
+
force_refresh=False,
|
|
1128
|
+
cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS,
|
|
1129
|
+
cache_only=False,
|
|
1130
|
+
status_timeout_seconds=None,
|
|
1131
|
+
):
|
|
1132
|
+
sessions = list_sessions()
|
|
1009
1133
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1134
|
+
cache_hits = {
|
|
1135
|
+
s["name"]: _status_cache_hit(
|
|
1012
1136
|
s,
|
|
1013
1137
|
force_refresh=force_refresh,
|
|
1014
1138
|
cache_ttl_seconds=cache_ttl_seconds,
|
|
1139
|
+
cache_only=cache_only,
|
|
1015
1140
|
)
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1141
|
+
for s in sessions
|
|
1142
|
+
}
|
|
1143
|
+
if progress_callback:
|
|
1144
|
+
progress_callback({
|
|
1145
|
+
"event": "status_started",
|
|
1146
|
+
"session_count": len(sessions),
|
|
1147
|
+
"check_count": sum(1 for cache_hit in cache_hits.values() if not cache_hit),
|
|
1148
|
+
})
|
|
1021
1149
|
|
|
1022
1150
|
resolved_by_name = {}
|
|
1023
1151
|
if sessions:
|
|
@@ -1032,7 +1160,14 @@ def create_session_service(options=None):
|
|
|
1032
1160
|
"session_name": s["name"],
|
|
1033
1161
|
"provider": s["provider"],
|
|
1034
1162
|
})
|
|
1035
|
-
futures[executor.submit(
|
|
1163
|
+
futures[executor.submit(
|
|
1164
|
+
_resolve_row_session,
|
|
1165
|
+
s,
|
|
1166
|
+
force_refresh=force_refresh,
|
|
1167
|
+
cache_ttl_seconds=cache_ttl_seconds,
|
|
1168
|
+
cache_only=cache_only,
|
|
1169
|
+
status_timeout_seconds=status_timeout_seconds,
|
|
1170
|
+
)] = s
|
|
1036
1171
|
for future in as_completed(futures):
|
|
1037
1172
|
s = futures[future]
|
|
1038
1173
|
resolved = future.result()
|
|
@@ -1060,27 +1195,7 @@ def create_session_service(options=None):
|
|
|
1060
1195
|
|
|
1061
1196
|
rows = []
|
|
1062
1197
|
for s in resolved:
|
|
1063
|
-
|
|
1064
|
-
enabled = s.get("enabled", True) is not False
|
|
1065
|
-
row_status = status if enabled else None
|
|
1066
|
-
rows.append({
|
|
1067
|
-
"session_name": s["name"],
|
|
1068
|
-
"provider": s["provider"],
|
|
1069
|
-
"enabled": enabled,
|
|
1070
|
-
"active": bool(_session_runtime(s["name"])) if enabled else False,
|
|
1071
|
-
"status": "enabled" if enabled else "disabled",
|
|
1072
|
-
"auth_status": (s.get("auth") or {}).get("status") or "unknown",
|
|
1073
|
-
"auth_checked_at": _to_local_iso((s.get("auth") or {}).get("lastCheckedAt")),
|
|
1074
|
-
"remaining_5h_pct": _normalize_pct_value(row_status.get("remaining_5h_pct")) if row_status else None,
|
|
1075
|
-
"remaining_week_pct": _normalize_pct_value(row_status.get("remaining_week_pct")) if row_status else None,
|
|
1076
|
-
"credits": row_status.get("credits") if row_status else None,
|
|
1077
|
-
"available_pct": _compute_available_pct(row_status),
|
|
1078
|
-
"reset_5h_at": row_status.get("reset_5h_at") if row_status else None,
|
|
1079
|
-
"reset_week_at": row_status.get("reset_week_at") if row_status else None,
|
|
1080
|
-
"reset_at": row_status.get("reset_at") if row_status else None,
|
|
1081
|
-
"updated_at": _to_local_iso(s.get("lastStatusAt")),
|
|
1082
|
-
"last_launched_at": _to_local_iso(s.get("lastLaunchedAt")),
|
|
1083
|
-
})
|
|
1198
|
+
rows.append(_status_row_from_session(s))
|
|
1084
1199
|
if progress_callback:
|
|
1085
1200
|
progress_callback({
|
|
1086
1201
|
"event": "status_finished",
|
|
@@ -1295,6 +1410,7 @@ def create_session_service(options=None):
|
|
|
1295
1410
|
"record_launch_history": record_launch_history,
|
|
1296
1411
|
"get_launch_history": get_launch_history,
|
|
1297
1412
|
"update_auth_state": update_auth_state,
|
|
1413
|
+
"get_status_row": get_status_row,
|
|
1298
1414
|
"get_status_rows": get_status_rows,
|
|
1299
1415
|
"format_list_rows": format_list_rows,
|
|
1300
1416
|
"get_session_auth_home": get_session_auth_home,
|