cdx-manager 0.5.5 → 0.5.6
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 +4 -4
- package/changelogs/CHANGELOGS_0_5_6.md +42 -0
- package/package.json +3 -1
- package/pyproject.toml +1 -1
- package/src/cli.py +3 -3
- package/src/cli_commands.py +24 -1
- package/src/notify.py +50 -17
- package/src/session_service.py +51 -13
- package/src/status_view.py +11 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CDX Manager
|
|
2
2
|
|
|
3
|
-
[](LICENSE) ](LICENSE)  
|
|
4
4
|
|
|
5
5
|
**Run multiple Codex and Claude sessions from one terminal. Switch between accounts instantly.**
|
|
6
6
|
|
|
@@ -126,7 +126,7 @@ For a specific version:
|
|
|
126
126
|
|
|
127
127
|
```bash
|
|
128
128
|
curl -fsSL https://raw.githubusercontent.com/AlexAgo83/cdx-manager/main/install.sh -o install.sh
|
|
129
|
-
CDX_VERSION=v0.5.
|
|
129
|
+
CDX_VERSION=v0.5.6 sh install.sh
|
|
130
130
|
```
|
|
131
131
|
|
|
132
132
|
From source:
|
|
@@ -258,8 +258,8 @@ cdx status
|
|
|
258
258
|
| `cdx doctor [--json]` | Inspect CLI dependencies, CDX_HOME permissions, missing state, orphan profiles, and pending quarantines |
|
|
259
259
|
| `cdx repair [--dry-run] [--force] [--json]` | Plan or apply safe repairs for missing state files, quarantines, and orphan profiles |
|
|
260
260
|
| `cdx update [--check] [--yes] [--json] [--version TAG]` | Update cdx-manager using the installer that matches how it was installed |
|
|
261
|
-
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--json]` | Wait for a session reset time and send a desktop notification when due |
|
|
262
|
-
| `cdx notify --next-ready [--poll seconds] [--once] [--json]` | Wait until the recommended session is usable or needs a refresh after reset |
|
|
261
|
+
| `cdx notify <name> --at-reset [--poll seconds] [--once] [--refresh] [--json]` | Wait for a session reset time and send a desktop notification when due |
|
|
262
|
+
| `cdx notify --next-ready [--poll seconds] [--once] [--refresh] [--json]` | Wait until the recommended session is usable or needs a refresh after reset |
|
|
263
263
|
| `cdx status [--json] [--refresh]` | Show token usage table for all sessions; JSON returns a versioned payload with structured warnings |
|
|
264
264
|
| `cdx status --small [--refresh]` / `cdx status -s [--refresh]` | Show compact token usage table without provider, blocking quota, credits, and updated columns |
|
|
265
265
|
| `cdx status <name> [--json] [--refresh]` | Show detailed usage breakdown for one session |
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog (`0.5.5 -> 0.5.6`)
|
|
2
|
+
|
|
3
|
+
Release date: 2026-05-24
|
|
4
|
+
|
|
5
|
+
## Major Highlights
|
|
6
|
+
|
|
7
|
+
- Reduced noisy `cdx status` progress output by respecting fresh cached status rows.
|
|
8
|
+
- Stopped probing disabled sessions during status resolution and hid stale quota metrics for disabled rows.
|
|
9
|
+
- Refined `cdx notify --next-ready` so it waits for the next blocked account reset instead of notifying for an account that is already usable.
|
|
10
|
+
|
|
11
|
+
## Status Output
|
|
12
|
+
|
|
13
|
+
- Added status-row cache handling so recently resolved rows do not print redundant `Checking ...` progress lines.
|
|
14
|
+
- Kept Claude status cache behavior aligned with the Claude refresh TTL, avoiding unnecessary Claude checks while cached usage is still fresh.
|
|
15
|
+
- Treated disabled sessions as display-only in `cdx status`; disabled sessions remain visible but are not refreshed or re-read from status artifacts.
|
|
16
|
+
- Rendered disabled-session quota columns as `-` for `OK`, `5H`, `WEEK`, `BLOCK`, `CR`, `RESET 5H`, and `RESET WEEK`.
|
|
17
|
+
- Preserved the stored last status internally so re-enabled sessions can resume normal status behavior.
|
|
18
|
+
|
|
19
|
+
## Notifications
|
|
20
|
+
|
|
21
|
+
- Added `--refresh` support to `cdx notify` so notification checks can force status refreshes when requested.
|
|
22
|
+
- Added text-mode progress output for notification checks while keeping `--json` output clean.
|
|
23
|
+
- Made `cdx notify --next-ready` ignore disabled sessions.
|
|
24
|
+
- Made `cdx notify --next-ready` ignore sessions that are already usable and instead target the next currently blocked session with a known reset.
|
|
25
|
+
- Returned `No upcoming session reset available` when there is no blocked session with a reset to wait for.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
- Updated package metadata, CLI version output, README badge, and pinned installer example to `v0.5.6`.
|
|
30
|
+
- Documented `--refresh` for `cdx notify` command examples.
|
|
31
|
+
|
|
32
|
+
## Validation and Regression Coverage
|
|
33
|
+
|
|
34
|
+
- Added regression coverage for cached Codex status rows suppressing redundant checking progress.
|
|
35
|
+
- Added regression coverage for Claude cache TTL behavior.
|
|
36
|
+
- Added regression coverage for disabled sessions skipping refresh and hiding public status metrics.
|
|
37
|
+
- Added regression coverage for `notify --next-ready` ignoring already-available and disabled sessions.
|
|
38
|
+
|
|
39
|
+
## Validation and Regression Evidence
|
|
40
|
+
|
|
41
|
+
- `npm run test:py`
|
|
42
|
+
- `./bin/cdx status`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdx-manager",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "Terminal session manager for Codex and Claude accounts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alexandre Agostini",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"bin",
|
|
28
|
+
"!bin/__pycache__",
|
|
29
|
+
"!bin/**/*.pyc",
|
|
28
30
|
"checksums",
|
|
29
31
|
"changelogs",
|
|
30
32
|
"src",
|
package/pyproject.toml
CHANGED
package/src/cli.py
CHANGED
|
@@ -49,7 +49,7 @@ from .status_view import (
|
|
|
49
49
|
)
|
|
50
50
|
from .update_check import check_for_update
|
|
51
51
|
|
|
52
|
-
VERSION = "0.5.
|
|
52
|
+
VERSION = "0.5.6"
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
# ---------------------------------------------------------------------------
|
|
@@ -83,8 +83,8 @@ def _print_help(use_color=False):
|
|
|
83
83
|
f" {_style('cdx doctor [--json]', '36', use_color)}",
|
|
84
84
|
f" {_style('cdx repair [--dry-run] [--force] [--json]', '36', use_color)}",
|
|
85
85
|
f" {_style('cdx update [--check] [--yes] [--json] [--version TAG]', '36', use_color)}",
|
|
86
|
-
f" {_style('cdx notify <name> --at-reset [--json]', '36', use_color)}",
|
|
87
|
-
f" {_style('cdx notify --next-ready [--json]', '36', use_color)}",
|
|
86
|
+
f" {_style('cdx notify <name> --at-reset [--refresh] [--json]', '36', use_color)}",
|
|
87
|
+
f" {_style('cdx notify --next-ready [--refresh] [--json]', '36', use_color)}",
|
|
88
88
|
f" {_style('cdx <name> [--json]', '36', use_color)}",
|
|
89
89
|
f" {_style('cdx --help', '36', use_color)}",
|
|
90
90
|
f" {_style('cdx --version', '36', use_color)}",
|
package/src/cli_commands.py
CHANGED
|
@@ -139,6 +139,25 @@ def _make_status_progress(ctx):
|
|
|
139
139
|
return progress
|
|
140
140
|
|
|
141
141
|
|
|
142
|
+
def _make_notify_progress(ctx):
|
|
143
|
+
def progress(event):
|
|
144
|
+
kind = event.get("event")
|
|
145
|
+
if kind == "notify_check_started":
|
|
146
|
+
target = event.get("session_name") or "next ready session"
|
|
147
|
+
ctx["out"](f"{_info(f'Checking notification target: {target}...', ctx['use_color'])}\n")
|
|
148
|
+
elif kind == "status_started":
|
|
149
|
+
message = f"Loading status for {event.get('session_count', 0)} session(s)..."
|
|
150
|
+
ctx["out"](f"{_dim(message, ctx['use_color'])}\n")
|
|
151
|
+
elif kind == "session_started":
|
|
152
|
+
provider = event.get("provider") or "session"
|
|
153
|
+
message = f"Checking {event.get('session_name')} ({provider})..."
|
|
154
|
+
ctx["out"](f"{_dim(message, ctx['use_color'])}\n")
|
|
155
|
+
elif kind == "notify_waiting":
|
|
156
|
+
message = f"{event.get('message')}; checking again in {event.get('poll')}s..."
|
|
157
|
+
ctx["out"](f"{_dim(message, ctx['use_color'])}\n")
|
|
158
|
+
return progress
|
|
159
|
+
|
|
160
|
+
|
|
142
161
|
def _latest_launch_transcript_path(session):
|
|
143
162
|
paths = _list_launch_transcript_paths(session)
|
|
144
163
|
if not paths:
|
|
@@ -626,6 +645,7 @@ def handle_notify(rest, ctx):
|
|
|
626
645
|
notifier=notifier,
|
|
627
646
|
sleep_fn=ctx["options"].get("sleep"),
|
|
628
647
|
now_fn=ctx["options"].get("now"),
|
|
648
|
+
progress_callback=None if parsed["json"] else _make_notify_progress(ctx),
|
|
629
649
|
)
|
|
630
650
|
if parsed["json"]:
|
|
631
651
|
_write_json(ctx, _json_success("notify", "Resolved notification event", event=event))
|
|
@@ -767,7 +787,10 @@ def handle_status(rest, ctx):
|
|
|
767
787
|
]
|
|
768
788
|
|
|
769
789
|
status_progress = None if parsed["json"] else _make_status_progress(ctx)
|
|
770
|
-
rows = ctx["service"]["get_status_rows"](
|
|
790
|
+
rows = ctx["service"]["get_status_rows"](
|
|
791
|
+
progress_callback=status_progress,
|
|
792
|
+
force_refresh=parsed["refresh"],
|
|
793
|
+
)
|
|
771
794
|
if len(args) == 0:
|
|
772
795
|
if parsed["json"]:
|
|
773
796
|
_write_json(ctx, _json_success("status", "Collected session status rows", warnings=warnings, rows=rows))
|
package/src/notify.py
CHANGED
|
@@ -4,7 +4,12 @@ import subprocess
|
|
|
4
4
|
import time
|
|
5
5
|
|
|
6
6
|
from .errors import CdxError
|
|
7
|
-
from .status_view import
|
|
7
|
+
from .status_view import (
|
|
8
|
+
PRIORITY_EMPTY_AVAILABLE_THRESHOLD,
|
|
9
|
+
_parse_reset_timestamp,
|
|
10
|
+
_priority_reset_timestamp,
|
|
11
|
+
_recommend_priority_sessions,
|
|
12
|
+
)
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
def parse_notify_args(args):
|
|
@@ -12,17 +17,18 @@ def parse_notify_args(args):
|
|
|
12
17
|
once = "--once" in args
|
|
13
18
|
at_reset = "--at-reset" in args
|
|
14
19
|
next_ready = "--next-ready" in args
|
|
20
|
+
refresh = "--refresh" in args
|
|
15
21
|
poll = 60
|
|
16
22
|
cleaned = []
|
|
17
23
|
i = 0
|
|
18
24
|
while i < len(args):
|
|
19
25
|
arg = args[i]
|
|
20
|
-
if arg in ("--json", "--once", "--at-reset", "--next-ready"):
|
|
26
|
+
if arg in ("--json", "--once", "--at-reset", "--next-ready", "--refresh"):
|
|
21
27
|
i += 1
|
|
22
28
|
continue
|
|
23
29
|
if arg == "--poll":
|
|
24
30
|
if i + 1 >= len(args):
|
|
25
|
-
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] | cdx notify --next-ready [--poll seconds] [--once]")
|
|
31
|
+
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] [--refresh] | cdx notify --next-ready [--poll seconds] [--once] [--refresh]")
|
|
26
32
|
try:
|
|
27
33
|
poll = max(1, int(args[i + 1]))
|
|
28
34
|
except ValueError as error:
|
|
@@ -37,48 +43,68 @@ def parse_notify_args(args):
|
|
|
37
43
|
i += 1
|
|
38
44
|
continue
|
|
39
45
|
if arg.startswith("-"):
|
|
40
|
-
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] | cdx notify --next-ready [--poll seconds] [--once]")
|
|
46
|
+
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] [--refresh] | cdx notify --next-ready [--poll seconds] [--once] [--refresh]")
|
|
41
47
|
cleaned.append(arg)
|
|
42
48
|
i += 1
|
|
43
49
|
if at_reset == next_ready:
|
|
44
|
-
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] | cdx notify --next-ready [--poll seconds] [--once]")
|
|
50
|
+
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] [--refresh] | cdx notify --next-ready [--poll seconds] [--once] [--refresh]")
|
|
45
51
|
if at_reset and len(cleaned) != 1:
|
|
46
|
-
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once]")
|
|
52
|
+
raise CdxError("Usage: cdx notify <name> --at-reset [--poll seconds] [--once] [--refresh]")
|
|
47
53
|
if next_ready and cleaned:
|
|
48
|
-
raise CdxError("Usage: cdx notify --next-ready [--poll seconds] [--once]")
|
|
54
|
+
raise CdxError("Usage: cdx notify --next-ready [--poll seconds] [--once] [--refresh]")
|
|
49
55
|
return {
|
|
50
56
|
"name": cleaned[0] if cleaned else None,
|
|
51
57
|
"mode": "at-reset" if at_reset else "next-ready",
|
|
52
58
|
"poll": poll,
|
|
53
59
|
"once": once,
|
|
54
60
|
"json": json_flag,
|
|
61
|
+
"refresh": refresh,
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
|
|
58
|
-
def wait_for_notification_event(service, parsed, notifier=None, sleep_fn=None, now_fn=None):
|
|
65
|
+
def wait_for_notification_event(service, parsed, notifier=None, sleep_fn=None, now_fn=None, progress_callback=None):
|
|
59
66
|
notifier = notifier or send_desktop_notification
|
|
60
67
|
sleep_fn = sleep_fn or time.sleep
|
|
61
68
|
now_fn = now_fn or time.time
|
|
62
69
|
while True:
|
|
63
|
-
|
|
70
|
+
if progress_callback:
|
|
71
|
+
progress_callback({"event": "notify_check_started", "mode": parsed["mode"], "session_name": parsed["name"]})
|
|
72
|
+
event = resolve_notify_event(
|
|
73
|
+
service["get_status_rows"](
|
|
74
|
+
progress_callback=progress_callback,
|
|
75
|
+
force_refresh=parsed.get("refresh", False),
|
|
76
|
+
),
|
|
77
|
+
parsed,
|
|
78
|
+
now_fn(),
|
|
79
|
+
)
|
|
64
80
|
if event["ready"] or parsed["once"]:
|
|
65
81
|
if event["ready"]:
|
|
66
82
|
notifier(event["title"], event["message"])
|
|
67
83
|
return event
|
|
84
|
+
if progress_callback:
|
|
85
|
+
progress_callback({
|
|
86
|
+
"event": "notify_waiting",
|
|
87
|
+
"message": event["message"],
|
|
88
|
+
"poll": parsed["poll"],
|
|
89
|
+
"session_name": event.get("session"),
|
|
90
|
+
"target_timestamp": event.get("target_timestamp"),
|
|
91
|
+
})
|
|
68
92
|
sleep_fn(parsed["poll"])
|
|
69
93
|
|
|
70
94
|
|
|
71
95
|
def resolve_notify_event(rows, parsed, now_ts=None):
|
|
72
96
|
now_ts = time.time() if now_ts is None else now_ts
|
|
73
97
|
if parsed["mode"] == "next-ready":
|
|
74
|
-
|
|
98
|
+
active_rows = [row for row in rows if row.get("enabled", True) is not False]
|
|
99
|
+
priority = _recommend_priority_sessions([
|
|
100
|
+
row for row in active_rows
|
|
101
|
+
if not _is_usable_now(row) and _priority_reset_timestamp(row) is not None
|
|
102
|
+
])
|
|
75
103
|
if not priority:
|
|
76
|
-
return _event(False, "cdx", "No session
|
|
104
|
+
return _event(False, "cdx", "No upcoming session reset available", None)
|
|
77
105
|
first = priority[0]
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
timestamp = _next_reset_timestamp(first)
|
|
81
|
-
if timestamp is not None and timestamp <= now_ts:
|
|
106
|
+
timestamp = _priority_reset_timestamp(first)
|
|
107
|
+
if _needs_refresh(first, timestamp, now_ts):
|
|
82
108
|
return _event(True, "cdx", f"{first['session_name']} reset is due; refresh status", first["session_name"])
|
|
83
109
|
return _event(False, "cdx", f"Waiting for {first['session_name']}", first["session_name"], timestamp)
|
|
84
110
|
|
|
@@ -93,9 +119,16 @@ def resolve_notify_event(rows, parsed, now_ts=None):
|
|
|
93
119
|
return _event(False, "cdx", f"Waiting for {row['session_name']} reset", row["session_name"], timestamp)
|
|
94
120
|
|
|
95
121
|
|
|
96
|
-
def
|
|
122
|
+
def _is_usable_now(row):
|
|
123
|
+
value = row.get("available_pct")
|
|
124
|
+
return value is not None and value > PRIORITY_EMPTY_AVAILABLE_THRESHOLD
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _needs_refresh(row, reset_timestamp, now_ts):
|
|
97
128
|
value = row.get("available_pct")
|
|
98
|
-
|
|
129
|
+
if value is None or value > PRIORITY_EMPTY_AVAILABLE_THRESHOLD:
|
|
130
|
+
return False
|
|
131
|
+
return reset_timestamp is not None and reset_timestamp < now_ts
|
|
99
132
|
|
|
100
133
|
|
|
101
134
|
def _next_reset_timestamp(row):
|
package/src/session_service.py
CHANGED
|
@@ -45,6 +45,8 @@ RESERVED_SESSION_NAMES = {
|
|
|
45
45
|
"--version",
|
|
46
46
|
"-v",
|
|
47
47
|
}
|
|
48
|
+
STATUS_CACHE_TTL_SECONDS = 60
|
|
49
|
+
CLAUDE_STATUS_CACHE_TTL_SECONDS = 10 * 60
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
def _encode(name):
|
|
@@ -133,6 +135,24 @@ def _parse_status_timestamp(value):
|
|
|
133
135
|
return None
|
|
134
136
|
|
|
135
137
|
|
|
138
|
+
def _status_cache_ttl_seconds(session, ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
139
|
+
if session.get("provider") == PROVIDER_CLAUDE and ttl_seconds == STATUS_CACHE_TTL_SECONDS:
|
|
140
|
+
return CLAUDE_STATUS_CACHE_TTL_SECONDS
|
|
141
|
+
return ttl_seconds
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _is_status_cache_fresh(session, ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
145
|
+
status = session.get("lastStatus") or {}
|
|
146
|
+
if _is_low_confidence_status_source(status):
|
|
147
|
+
return False
|
|
148
|
+
updated_at = _parse_status_timestamp(status.get("updated_at") or session.get("lastStatusAt"))
|
|
149
|
+
if not updated_at:
|
|
150
|
+
return False
|
|
151
|
+
now = datetime.now(timezone.utc).astimezone()
|
|
152
|
+
ttl_seconds = _status_cache_ttl_seconds(session, ttl_seconds)
|
|
153
|
+
return (now - updated_at.astimezone(now.tzinfo)).total_seconds() < ttl_seconds
|
|
154
|
+
|
|
155
|
+
|
|
136
156
|
def _is_status_newer(candidate, current):
|
|
137
157
|
if not candidate:
|
|
138
158
|
return False
|
|
@@ -572,8 +592,12 @@ def create_session_service(options=None):
|
|
|
572
592
|
raise CdxError(f"Unknown session: {name}")
|
|
573
593
|
return updated
|
|
574
594
|
|
|
575
|
-
def _resolve_session_status(session):
|
|
595
|
+
def _resolve_session_status(session, force_refresh=False, cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
576
596
|
current_status = session.get("lastStatus")
|
|
597
|
+
if session.get("enabled", True) is False:
|
|
598
|
+
return current_status
|
|
599
|
+
if current_status and not force_refresh and _is_status_cache_fresh(session, ttl_seconds=cache_ttl_seconds):
|
|
600
|
+
return current_status
|
|
577
601
|
source_root = session.get("authHome") or _get_session_auth_home(
|
|
578
602
|
session["name"], session["provider"]
|
|
579
603
|
)
|
|
@@ -644,7 +668,7 @@ def create_session_service(options=None):
|
|
|
644
668
|
raise CdxError(f"Unknown session: {name}")
|
|
645
669
|
return updated
|
|
646
670
|
|
|
647
|
-
def get_status_rows(progress_callback=None):
|
|
671
|
+
def get_status_rows(progress_callback=None, force_refresh=False, cache_ttl_seconds=STATUS_CACHE_TTL_SECONDS):
|
|
648
672
|
sessions = list_sessions()
|
|
649
673
|
if progress_callback:
|
|
650
674
|
progress_callback({
|
|
@@ -653,13 +677,25 @@ def create_session_service(options=None):
|
|
|
653
677
|
})
|
|
654
678
|
resolved = []
|
|
655
679
|
for s in sessions:
|
|
656
|
-
|
|
680
|
+
cache_hit = (
|
|
681
|
+
s.get("enabled", True) is False
|
|
682
|
+
or (
|
|
683
|
+
s.get("lastStatus")
|
|
684
|
+
and not force_refresh
|
|
685
|
+
and _is_status_cache_fresh(s, ttl_seconds=cache_ttl_seconds)
|
|
686
|
+
)
|
|
687
|
+
)
|
|
688
|
+
if progress_callback and not cache_hit:
|
|
657
689
|
progress_callback({
|
|
658
690
|
"event": "session_started",
|
|
659
691
|
"session_name": s["name"],
|
|
660
692
|
"provider": s["provider"],
|
|
661
693
|
})
|
|
662
|
-
status = _resolve_session_status(
|
|
694
|
+
status = _resolve_session_status(
|
|
695
|
+
s,
|
|
696
|
+
force_refresh=force_refresh,
|
|
697
|
+
cache_ttl_seconds=cache_ttl_seconds,
|
|
698
|
+
)
|
|
663
699
|
if progress_callback:
|
|
664
700
|
progress_callback({
|
|
665
701
|
"event": "session_finished",
|
|
@@ -687,18 +723,20 @@ def create_session_service(options=None):
|
|
|
687
723
|
rows = []
|
|
688
724
|
for s in resolved:
|
|
689
725
|
status = s.get("lastStatus")
|
|
726
|
+
enabled = s.get("enabled", True) is not False
|
|
727
|
+
row_status = status if enabled else None
|
|
690
728
|
rows.append({
|
|
691
729
|
"session_name": s["name"],
|
|
692
730
|
"provider": s["provider"],
|
|
693
|
-
"enabled":
|
|
694
|
-
"status": "
|
|
695
|
-
"remaining_5h_pct":
|
|
696
|
-
"remaining_week_pct":
|
|
697
|
-
"credits":
|
|
698
|
-
"available_pct": _compute_available_pct(
|
|
699
|
-
"reset_5h_at":
|
|
700
|
-
"reset_week_at":
|
|
701
|
-
"reset_at":
|
|
731
|
+
"enabled": enabled,
|
|
732
|
+
"status": "enabled" if enabled else "disabled",
|
|
733
|
+
"remaining_5h_pct": row_status.get("remaining_5h_pct") if row_status else None,
|
|
734
|
+
"remaining_week_pct": row_status.get("remaining_week_pct") if row_status else None,
|
|
735
|
+
"credits": row_status.get("credits") if row_status else None,
|
|
736
|
+
"available_pct": _compute_available_pct(row_status),
|
|
737
|
+
"reset_5h_at": row_status.get("reset_5h_at") if row_status else None,
|
|
738
|
+
"reset_week_at": row_status.get("reset_week_at") if row_status else None,
|
|
739
|
+
"reset_at": row_status.get("reset_at") if row_status else None,
|
|
702
740
|
"updated_at": _to_local_iso(s.get("lastStatusAt")),
|
|
703
741
|
"last_launched_at": _to_local_iso(s.get("lastLaunchedAt")),
|
|
704
742
|
})
|
package/src/status_view.py
CHANGED
|
@@ -82,17 +82,20 @@ def _format_status_rows(rows, use_color=False, small=False):
|
|
|
82
82
|
base.append(r.get("provider") or "n/a")
|
|
83
83
|
status = r.get("status") or ("enabled" if r.get("enabled", True) else "disabled")
|
|
84
84
|
base.append(_style(status, "2" if status == "disabled" else "32", use_color))
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
if r.get("enabled", True) is False:
|
|
86
|
+
usage_columns = [_style("-", "2", use_color)] * 5
|
|
87
|
+
else:
|
|
88
|
+
usage_columns = [
|
|
89
|
+
_style_pct(r.get("available_pct"), use_color),
|
|
90
|
+
_style_pct(r.get("remaining_5h_pct"), use_color),
|
|
91
|
+
_style_pct(r.get("remaining_week_pct"), use_color),
|
|
92
|
+
_style_reset_time(r.get("reset_5h_at"), use_color),
|
|
93
|
+
_style_reset_time(r.get("reset_week_at"), use_color),
|
|
94
|
+
]
|
|
92
95
|
if small:
|
|
93
96
|
base += usage_columns
|
|
94
97
|
else:
|
|
95
|
-
block = _format_blocking_quota(r)
|
|
98
|
+
block = "-" if r.get("enabled", True) is False else _format_blocking_quota(r)
|
|
96
99
|
credits = str(r["credits"]) if r.get("credits") is not None else "-"
|
|
97
100
|
base += usage_columns[:3] + [
|
|
98
101
|
_style(block, "33" if block not in ("?", "-") else "2", use_color),
|